{ "chunk": "game_ovl_6F00_A_74EE", "unit": "game/ovl_6F00_A", "routines": [ { "addr": "74FE", "name": "clearMapMarkers", "summary": "Map generator clean-up pass: scans all 1600 cells of the 40x40 map ($F000-$F63F, six full pages by self-modified pointer then the 64-byte tail at $F600) and turns every cell whose value equals the marker byte in A (bit 7 set: $80 or $81) back into blank terrain $40. generateBattlefieldMap (sub_6F04) calls it with A=$80 after the first terrain pass and with A=$81 after the second, removing the river reservation markers placed by markRiverBanks.", "inputs": "A = marker value to erase ($80 / $81); map at $F000", "outputs": "map cells equal to A become $40; operands at $7517/$7522 (page) and $751C/$753C (marker) self-modified; A/Y clobbered", "confidence": "high" }, { "addr": "7548", "name": "generateRiver", "summary": "Draws the river (terrain codes $55-$5C: class 1, move cost 65, cyan) across the map from column 0 to 39 as part of generateBattlefieldMap. No river for QB Sneak (mapGenGameFlags bit 7). Start row: 20 on symmetric maps (mapSymmetryFlags bit 7), otherwise random 12..27; Defender (bit 6) always gets a river, other types skip it with probability 1/8. Each step writes $55 + current segment type at (genCol, genRow), picks the next segment (chooseNextRiverSegment), reserves the banks (markRiverBanks) and moves (advanceRiverPosition) until genCol reaches 40.", "inputs": "mapGenGameFlags $032E, mapSymmetryFlags $032D, scenario RNG $5A-$5C (nextScenarioRandom $FD4A), map at $F000", "outputs": "river tiles written to the map, $80/$81 reservation markers around them, genCol/genRow/riverCurSegment/genTileType updated, may set mapSymmetryFlags bit 6", "confidence": "high" }, { "addr": "759E", "name": "chooseNextRiverSegment", "summary": "Chooses the next river segment type into genTileType ($0312). Types: 0 = bend down/right ($55), 1 = bend left/down ($56), 2 = bend up/right ($57), 3 = bend left/up ($58), 4-7 = horizontal straight variants ($59-$5C). Rules: after 1 comes 2, after 3 comes 0, after a bend a straight, after a straight 50% straight / else 1 or 3; columns 0-1 and 38-39 are forced horizontal (column 37 may not start a vertical jog). Symmetric maps stay horizontal on row 20; at column 20 with probability 7/8 the river jogs one row up (genCol -= 2, genRow = 19, mapSymmetryFlags |= $40) so that the 180-degree mirrored bottom half joins it at the centre.", "inputs": "riverCurSegment $0311, genCol $030F, genRow $0310, mapSymmetryFlags $032D, scenario RNG", "outputs": "genTileType $0312 = next segment 0-7; may modify genCol/genRow and set $032D bit 6", "confidence": "high" }, { "addr": "761C", "name": "advanceRiverPosition", "summary": "Moves the river cursor according to the current segment type riverCurSegment: patches the JMP operand at $762C with riverStepLo/Hi[type] and jumps to one of the three stubs riverStepRight ($762E, genCol+1), riverStepUp ($7632, genRow-1) or riverStepDown ($7636, genRow+1). Types 1 -> down, 3 -> up, all others -> right.", "inputs": "riverCurSegment $0311, tables $763A/$7642", "outputs": "genCol or genRow changed; operand at $762C self-modified", "confidence": "high" }, { "addr": "762E", "name": "riverStepRight", "summary": "Step stub reached through advanceRiverPosition: INC genCol; RTS. Listed as data (.byte) in the disassembly.", "inputs": "none", "outputs": "genCol $030F + 1", "confidence": "high" }, { "addr": "7632", "name": "riverStepUp", "summary": "Step stub reached through advanceRiverPosition: DEC genRow; RTS (segment type 3). Listed as data in the disassembly.", "inputs": "none", "outputs": "genRow $0310 - 1", "confidence": "high" }, { "addr": "7636", "name": "riverStepDown", "summary": "Step stub reached through advanceRiverPosition: INC genRow; RTS (segment type 1). Listed as data in the disassembly.", "inputs": "none", "outputs": "genRow $0310 + 1", "confidence": "high" }, { "addr": "764A", "name": "markRiverBanks", "summary": "Reserves the cells around the current river cell so that later terrain passes keep the banks clear: for columns 2-37 it addresses the 6x6 block whose origin is (genCol & ~1) - 2, (genRow & ~1) - 2, marks the 16 inner cells (riverZoneInnerOffsets) with $81 when they are blank or already marked, and the 20 perimeter cells (riverZoneOuterOffsets) with $80 when blank. In columns 0-1 and 38-39 only the four cells of the river's own column (rows evenRow-1..evenRow+2) get $81. The markers are removed again by clearMapMarkers.", "inputs": "genCol $030F, genRow $0310, map via readMapCell ($25FA -> zp_48/49), tables $7894/$78A8", "outputs": "$80/$81 written into blank map cells; zp_48/49 pointer", "confidence": "high" }, { "addr": "76AD", "name": "mapNameEntryScreen", "summary": "'TYPE MAP NAME' screen of the game setup (entered by JMP from $8339 when the map item is chosen). Draws the current map (generateBattlefieldMap, optionally flipped 180 degrees by $EC58 when mapFlipFlag is set), the overview map, clears the name field, prints 'TYPE MAP NAME / (F7 RANDOM) / (F5 SYM/RND)' ($0677) and 'BTN TO ACCEPT / (F1 TO FLIP)' ($069F), then loops on enterMapName: each completed name (C=0) becomes the new mapSeed, the map is cleared ($EC00), redrawn and the prompt repeated; the fire button (C=1) prints 'ACCEPTED' ($EFED) centred on row 15 and returns to the setup menu.", "inputs": "mapSeed $0B95-$0B97, mapFlipFlag $0B9D, keyboard/joystick", "outputs": "mapSeed, mapFlipFlag (cleared after a new name), map regenerated, text window/colour zp_3D/zp_42 = 9", "confidence": "high" }, { "addr": "7717", "name": "enterMapName", "summary": "Interactive entry of the 5-character map name at column 4, row 16 (field shown by printMapName, underlined with five $25 glyphs on the next row). Accepted characters are the 32-entry alphabet at $890C (space, A-Z, + - * / =), typed via newKeyEvent ($90EA); DEL ($88) erases, other keys beep (sound 3), a colour-$11 block marks the cursor cell. F1 (currentKey 0) toggles mapFlipFlag and flips/redraws the map. Once five characters are present they are packed 5 bits each (last character in the low bits) into $5A-$5C and the routine returns C=0. F7 (key 3) or F5 (key 2) instead take a random seed from the game RNG ($57-$59; F5 ORs $1F into the low byte = symmetric map), convert it with seedToMapName and return C=0. A fire-button press returns C=1 (accept). Also used by the copy-protection manualLookupCheck ($09F9) to read the manual answer.", "inputs": "currentKey zp_66, newKeyEvent $90EA, fire button (checkFirePressed $0EB1), rngState $57-$59, mapNameAlphabet $890C", "outputs": "C = 0: $5A-$5C = 24-bit seed and mapName $0BA9-$0BAD = its text; C = 1: fire pressed, nothing chosen; mapNameLength $0313, mapFlipFlag may toggle, zp_18 scratch, text cursor state", "confidence": "high" }, { "addr": "782D", "name": "setMapNameCursor", "summary": "Positions the text cursor at the map-name field: textCursorCol = 4, row 16 (sub_C097).", "inputs": "text window zp_3B/zp_40", "outputs": "zp_3F = 4, zp_40 = $10, screen pointer zp_B6/B7", "confidence": "high" }, { "addr": "7836", "name": "clearMapNameField", "summary": "Fills the five map-name characters $0BA9-$0BAD with spaces ($20).", "inputs": "none", "outputs": "$0BA9-$0BAD = $20; Y = $FF", "confidence": "high" }, { "addr": "7841", "name": "printMapName", "summary": "Moves the cursor to the name field (setMapNameCursor), terminates the name with $8D (CR with the end-of-string bit) at $0BAE and prints mapName $0BA9 with the text engine ($C133).", "inputs": "mapName $0BA9-$0BAD", "outputs": "$0BAE = $8D, text printed, cursor on the next row", "confidence": "high" }, { "addr": "7850", "name": "seedToMapName", "summary": "Converts the 24-bit seed in A (low), Y (mid), X (high) into the five-letter map name: five times take the low 5 bits as an index into the alphabet at $890C, store at $0BA9+4 down to $0BA9, shift the seed right by 5. Called with the map seed registers loaded by $EC2B (from generateBattlefieldMap) and by enterMapName after a random F5/F7 seed.", "inputs": "A/Y/X = seed bytes 0/1/2 (same layout as $5A/$5B/$5C)", "outputs": "mapName $0BA9-$0BAD; zp_18-zp_1A used as the shift register; X/Y clobbered", "confidence": "high" }, { "addr": "7872", "name": "randomTileVariant", "summary": "Picks a random visual variant of a terrain code: A = base tile code; the mask table at $0770 (indexed by code - $40, or code - $20 for codes below $40) gives the number of extra variants (0, 1 or 3); the result is A + (nextScenarioRandom AND mask). Y is preserved through the self-modified operand at $7892. Used by the terrain fill passes of the map generator ($72A4, $74E7). Starts with a NOP (probably a disabled byte).", "inputs": "A = base tile code, Y preserved, table $0770, scenario RNG", "outputs": "A = tile code with random variant bits; operands at $788D/$7890/$7892 self-modified", "confidence": "high" }, { "addr": "78D1", "name": "startGameSession", "summary": "Game-start sequence called from startGameFromSetup ($0B40) after the setup block and unit templates were loaded. Resets battle state (film limit, redraw/recycler/clock markers = $FF, unitCol[0] = $FF so the unit template gets copied, colours own=6/enemy=2, overview unit pattern $55, screen 3), then if filmPlaybackFlag bit 7 jumps to the film replay start ($7CD1). Otherwise: revealAll = trainer flag, step interval 4, decides the side (alternates via firstPlayFlag when preset, else negotiated over the link after openCommLink/waitForConnection or readyForNewGameHandshake), sends the side ($553B) and waits for the exchange, patches the own-side branches, player 0 sends the map seed with command $8B (or $A6 when flipped) while player 1 shows message $1A and waits for it (waitForMapSeedCommand), exchanges identities ($ED43, version check $90F8 == $DB else 'OPPONENT USING NON-STANDARD GAME.'), copies the unit start template for the game type ($ECF3), records the setup in the film header ($D000: +4 trainer flag, +5 side, +6 name, +$C options, +$11 owner names, +$2B seed, +$2E first-play/flip), generates the map, rotates map/units for player 1 ($EC58/$EC91), initialises all units, for the trainer build loads the $EC00 sub-overlay from track 29 and runs $EC0F, runs the unit setup phase (runMapMainLoop with gamePhase $3F) for game types other than Scrimmage and The Bomb, waits for the opponent's packet, sets the game clock from roundsByGameType, maps the comcen speed classes, writes the film snapshot, places the recyclers, shuffles the unit order, sets the last-quarter warning round and jumps into the comm tail at $EE26 to start the battle.", "inputs": "settings block $0B95-$0BB4, filmPlaybackFlag $0B9B, commSessionState $0B9C, commBuildFlag $0BA5, comm module state $E01D/$E03B, unit templates at $F000", "outputs": "entire battle state initialised (map, units, clock, film header, message slots, gamePhase zp_B1); does not return normally (ends in JMP $EE26 or $7CD1)", "confidence": "medium" }, { "addr": "7BB2", "name": "shuffleInitialUnitOrder", "summary": "Seeds the scenario RNG from mapSeed ($EC2B), resets unitBaseOrder $9164 and unitProcessOrder $90FD to the identity permutation ($7D70), performs 51 random swaps with shuffleUnitOrder ($47ED) and jumps to L_4657 (the round-order rebuild at the end of finishRound). Both machines get the same order because the RNG is seeded from the shared map seed.", "inputs": "mapSeed $0B95-$0B97", "outputs": "$9164/$90FD shuffled, scenario RNG $5A-$5C, zp_18 counter; continues at L_4657", "confidence": "high" }, { "addr": "7BC6", "name": "patchOwnSideBranches", "summary": "Installs the local player's side into the engine's self-modifying side tests: writes BCC ($90, side 0) or BCS ($B0, side 1) into the branch opcodes at $63E2, $2840, $3F9E, $36D5, $379C, $4783, $5BF7 and the inverted opcode into $206B, $2084, $3B2C, $3BD0 (all follow a CMP/CPY #$32 'unit index < 50' test), and sets comcenUnit $9236 = comcenUnitBySide[side] (49 or 99). Called at game start and at film replay start ($7D20).", "inputs": "playerSide $0B9F, comcenUnitBySide $0C8A", "outputs": "11 branch opcodes patched, $9236", "confidence": "high" }, { "addr": "7BF9", "name": "sendPhaseSyncAndWait", "summary": "Queues the 1-byte phase-sync command $9E (queueCmdBytePending), lets $C7C3 show 'WAITING FOR OPPONENT' (message $0B) when gamePhase bit 0 is set on a modem game, then runs frames (updateGameFrame when gamePhase bits 6-7 are clear, otherwise waitForExchangeIdle) until the $9E command has been executed on both machines, which clears the low two bits of gamePhase zp_B1 via getPauseClearMask. Finally clears messageState.", "inputs": "gamePhase zp_B1, comm exchange", "outputs": "zp_B1 low bits cleared by the command handler, $92A1 = 0", "confidence": "high" }, { "addr": "7C1C", "name": "waitForMapSeedCommand", "summary": "Calls waitForExchangeIdle repeatedly while gamePhase zp_B1 bit 7 is set; the bit is cleared by the handler of the map-seed command $8B/$A6 ($51EA), i.e. this waits until both machines have received the map seed. Then clears messageState $92A1.", "inputs": "zp_B1, comm exchange", "outputs": "$92A1 = 0", "confidence": "high" } ], "variables": [ { "addr": "18", "scope": "zp", "name": "tempByte18", "meaning": "scratch: typed character / alphabet index in enterMapName, seed shift byte 0 in seedToMapName, swap counter in shuffleInitialUnitOrder, negotiated side returned by the link code ($7971)", "confidence": "high" }, { "addr": "19", "scope": "zp", "name": "tempByte19", "meaning": "scratch: seed shift byte 1 in seedToMapName", "confidence": "high" }, { "addr": "1A", "scope": "zp", "name": "tempByte1A", "meaning": "scratch: seed shift byte 2 in seedToMapName", "confidence": "high" }, { "addr": "3D", "scope": "zp", "name": "textAttribute", "meaning": "current text colour byte (9 in the map-name screen, 0 before the setup phase); also written next to the cursor block in enterMapName", "confidence": "high" }, { "addr": "3E", "scope": "zp", "name": "textFillChar", "meaning": "fill glyph used by $C0EF: $25 for the five underline glyphs below the map-name field, restored to 0", "confidence": "high" }, { "addr": "3F", "scope": "zp", "name": "textCursorCol", "meaning": "text cursor column relative to the window (4 for the map-name field)", "confidence": "high" }, { "addr": "42", "scope": "zp", "name": "messageAttribute", "meaning": "text attribute used while a message is displayed (set to 9 with zp_3D in mapNameEntryScreen)", "confidence": "high" }, { "addr": "48", "scope": "zp", "name": "mapCellPtr", "meaning": "pointer (lo) to the map cell returned by readMapCell; the river generator writes tiles and markers through it", "confidence": "high" }, { "addr": "49", "scope": "zp", "name": "mapCellPtrHi", "meaning": "pointer (hi) of mapCellPtr", "confidence": "high" }, { "addr": "57", "scope": "zp", "name": "rngState", "meaning": "game RNG byte 0 (nextGameRandom $FD5D); source of random map seeds (F5/F7)", "confidence": "high" }, { "addr": "58", "scope": "zp", "name": "rngStateMid", "meaning": "game RNG byte 1, copied to $5B for a random map", "confidence": "high" }, { "addr": "59", "scope": "zp", "name": "rngStateHi", "meaning": "game RNG byte 2, copied to $5C for a random map", "confidence": "high" }, { "addr": "5A", "scope": "zp", "name": "scenarioSeed0", "meaning": "scenario RNG state byte 0 / low byte of the 24-bit map seed returned by enterMapName (low 5 bits = last name letter; $1F = symmetric map)", "confidence": "high" }, { "addr": "5B", "scope": "zp", "name": "scenarioSeed1", "meaning": "scenario RNG state / map seed byte 1", "confidence": "high" }, { "addr": "5C", "scope": "zp", "name": "scenarioSeed2", "meaning": "scenario RNG state / map seed byte 2 (only 4 bits of the first letter survive packing)", "confidence": "high" }, { "addr": "66", "scope": "zp", "name": "currentKey", "meaning": "key from the last poll: $FF none, 0-3 = F1/F3/F5/F7 (F1 flips the map, F5 symmetric random, F7 random in enterMapName); set to $FF by startGameSession", "confidence": "high" }, { "addr": "73", "scope": "zp", "name": "cmdParam0", "meaning": "command parameter 0: mapSeed byte 0 for the $8B/$A6 map-seed command", "confidence": "high" }, { "addr": "74", "scope": "zp", "name": "cmdParam1", "meaning": "command parameter 1: mapSeed byte 1", "confidence": "high" }, { "addr": "75", "scope": "zp", "name": "cmdParam2", "meaning": "command parameter 2: mapSeed byte 2", "confidence": "high" }, { "addr": "77", "scope": "zp", "name": "ownUnitColour", "meaning": "colour of the local player's units, set to 6 (blue) at game start", "confidence": "high" }, { "addr": "78", "scope": "zp", "name": "enemyUnitColour", "meaning": "colour of the opponent's units, set to 2 (red) at game start", "confidence": "high" }, { "addr": "B1", "scope": "zp", "name": "gamePhase", "meaning": "game phase flags: $FF at session start, bit 7 cleared by the map-seed command, bits 1-0 cleared by the $9E sync command, $7F during identity exchange, $3F during the unit setup phase, 0 when the battle starts (then DEC to $FF before the final sync, left for the comm tail)", "confidence": "medium" }, { "addr": "B6", "scope": "zp", "name": "screenCellPtr", "meaning": "pointer (lo, hi in $B7) to the colour byte of the text cursor cell; enterMapName writes $11 there to show the cursor block", "confidence": "high" }, { "addr": "030F", "scope": "abs", "name": "genCol", "meaning": "map generator: current column (river cursor x, 0-39)", "confidence": "high" }, { "addr": "0310", "scope": "abs", "name": "genRow", "meaning": "map generator: current row (river cursor y)", "confidence": "high" }, { "addr": "0311", "scope": "abs", "name": "riverCurSegment", "meaning": "river segment type of the cell just written (0-7); selects the move direction in advanceRiverPosition", "confidence": "high" }, { "addr": "0312", "scope": "abs", "name": "genTileType", "meaning": "map generator scratch: next river segment type (0-7, tile = $55 + type) in generateRiver; the terrain code being placed in the fill pass sub_73EE", "confidence": "high" }, { "addr": "0313", "scope": "abs", "name": "mapNameLength", "meaning": "number of characters typed so far into the map-name field (0-5)", "confidence": "high" }, { "addr": "032D", "scope": "abs", "name": "mapSymmetryFlags", "meaning": "bit 7 = symmetric map (seed low 5 bits = $1F, name ends in '='): river on row 20 and the bottom half is the 180-degree mirror of the top half; bit 6 = the river jogs from row 20 to row 19 at the centre (fixed up at $6F7F)", "confidence": "high" }, { "addr": "032E", "scope": "abs", "name": "mapGenGameFlags", "meaning": "map generator game-type flags set by generateBattlefieldMap: $80 = QB Sneak (no river, fewer features), $40 = Defender (river always present, fixed feature count), 0 otherwise", "confidence": "high" }, { "addr": "0B7D", "scope": "abs", "name": "inputLockoutTimer", "meaning": "frames during which the IRQ ignores input; $FF while the game start handshake runs", "confidence": "high" }, { "addr": "0B95", "scope": "abs", "name": "mapSeed", "meaning": "3-byte (24-bit) seed of the current battlefield ($0B95-$0B97); equals the 5-letter map name packed 5 bits per letter; sent to the opponent with command $8B/$A6 and copied to $5A-$5C by $EC2B before generating", "confidence": "high" }, { "addr": "0B98", "scope": "abs", "name": "prevMapSeed", "meaning": "3-byte copy of the map seed of the previous game; if unchanged the saved map name backup $0BAF is restored, otherwise firstPlayFlag is set to 1", "confidence": "high" }, { "addr": "0B9D", "scope": "abs", "name": "mapFlipFlag", "meaning": "0/1: the chosen map is shown rotated 180 degrees (F1 TO FLIP in the map-name screen); XORed with playerSide to decide whether the generated map is rotated, sent as command $A6 instead of $8B, recorded in the film header (+$2E bit 0)", "confidence": "high" }, { "addr": "0B9E", "scope": "abs", "name": "prevMapFlipFlag", "meaning": "mapFlipFlag of the previous game; a change sets firstPlayFlag", "confidence": "high" }, { "addr": "0B9F", "scope": "abs", "name": "playerSide", "meaning": "local side 0/1 ($FE/$FF = not chosen: then negotiated over the link into zp_18); alternates every second game via firstPlayFlag; side 0 chooses and sends the map seed", "confidence": "high" }, { "addr": "0BA0", "scope": "abs", "name": "playerSideSetup", "meaning": "copy of playerSide taken after the side was settled (read back by startGameFromSetup)", "confidence": "medium" }, { "addr": "0BA1", "scope": "abs", "name": "firstPlayFlag", "meaning": "nonzero = this side plays first; incremented each game start (wraps at 2 and flips the side), forced to 1 when the map, options or flip state changed; written to the film header +$2E bit 1", "confidence": "medium" }, { "addr": "0BA2", "scope": "abs", "name": "pendingFirstPlayFlag", "meaning": "copy of firstPlayFlag made at game start", "confidence": "high" }, { "addr": "0BA3", "scope": "abs", "name": "gameTypeOptions", "meaning": "bits 0-2 game type (0 Scrimmage,1 QB Sneak,2 The Bomb,3 Face-Off,4 Sluggers,5 Full War,6 Defender), bit 3 unit options, bit 4 damage, bit 6 custom; bit 7 (inferred) = hide the map name: terminator becomes '!' and player 1 sees '#####'", "confidence": "medium" }, { "addr": "0BA4", "scope": "abs", "name": "pendingGameTypeOptions", "meaning": "previous gameTypeOptions; a difference at game start sets firstPlayFlag = 1", "confidence": "high" }, { "addr": "0BA5", "scope": "abs", "name": "commBuildFlag", "meaning": "bit 7 = comm build 1 (track 35) resident = the built-in TRAINER opponent (its name 'TRAINER' at $884E is used instead of a link exchange, all units are revealed, the $EC00 sub-overlay from track 29 is run at game start)", "confidence": "high" }, { "addr": "0BA7", "scope": "abs", "name": "loadingDepth", "meaning": "incremented around the track-29 sub-overlay load so the IRQ suppresses input", "confidence": "high" }, { "addr": "0BA8", "scope": "abs", "name": "gameEndReason", "meaning": "cleared (game in progress) at session start", "confidence": "high" }, { "addr": "0BA9", "scope": "abs", "name": "mapName", "meaning": "5 ASCII characters of the map name ($0BA9-$0BAD) from the 32-letter alphabet; spaces when empty; '#####' for player 1 when the name is hidden", "confidence": "high" }, { "addr": "0BAE", "scope": "abs", "name": "mapNameTerminator", "meaning": "byte after the name: $8D (CR, end) while printing, '!'|$80 when the name is hidden, otherwise the previous value with bit 7 set", "confidence": "high" }, { "addr": "0BAF", "scope": "abs", "name": "mapNameBackup", "meaning": "6-byte copy of mapName+terminator kept with prevMapSeed; restored when the same seed is used again", "confidence": "high" }, { "addr": "0C8A", "scope": "abs", "name": "comcenUnitBySide", "meaning": "{$31,$63}: comcen unit index of side 0 and side 1, copied to $9236 by patchOwnSideBranches", "confidence": "high" }, { "addr": "4DF4", "scope": "abs", "name": "filmLastClock", "meaning": "self-modified operand of the film time-stamp code, reset to 0 at game start", "confidence": "high" }, { "addr": "57C3", "scope": "abs", "name": "filmLastRecordedClock", "meaning": "self-modified operand (L_57C2+1) holding the clock value of the last film record, reset to 0 at game start", "confidence": "medium" }, { "addr": "9022", "scope": "abs", "name": "spriteEnableShadow", "meaning": "cleared after the unit setup phase to hide the cursor sprites", "confidence": "high" }, { "addr": "90EA", "scope": "abs", "name": "newKeyEvent", "meaning": "fresh key press this frame ($FF none); enterMapName consumes it and resets it to $FF", "confidence": "high" }, { "addr": "90F8", "scope": "abs", "name": "selectedUnit", "meaning": "reused as the version-check result of the identity exchange: preset to $DB, $ED43 stores $E022 EOR $E023; a mismatch shows 'OPPONENT USING NON-STANDARD GAME.'", "confidence": "high" }, { "addr": "90FB", "scope": "abs", "name": "currentScreen", "meaning": "set to 3 (menu screen) at session start", "confidence": "high" }, { "addr": "91CB", "scope": "abs", "name": "roundsRemaining", "meaning": "game clock; $FF during setup, then roundsByGameType[type] when the battle starts", "confidence": "high" }, { "addr": "9224", "scope": "abs", "name": "overlayBParam9224", "meaning": "set to $0B at game start; read only by overlay B ($7421, copied to $9237)", "confidence": "low" }, { "addr": "9225", "scope": "abs", "name": "overlayBParam9225", "meaning": "set to 9 at game start; read only by overlay B ($7427, copied to $9238)", "confidence": "low" }, { "addr": "922B", "scope": "abs", "name": "overviewUnitPattern", "meaning": "$55 = multicolour pixel pattern used for own units on the overview map (EOR $FF for enemy units in sub_5BDA)", "confidence": "medium" }, { "addr": "9236", "scope": "abs", "name": "comcenUnit", "meaning": "index of the local player's comcen (49 or 99) set by patchOwnSideBranches", "confidence": "high" }, { "addr": "923A", "scope": "abs", "name": "pendingRedrawGroup", "meaning": "reset to $FF (none) at session start", "confidence": "high" }, { "addr": "923B", "scope": "abs", "name": "pendingRedrawUnit", "meaning": "reset to $FF (none) at session start", "confidence": "high" }, { "addr": "9242", "scope": "abs", "name": "unitDrawOverride", "meaning": "cleared before the unit setup phase", "confidence": "high" }, { "addr": "929D", "scope": "abs", "name": "lastOrderedGroupBits", "meaning": "reset to $FF at session start", "confidence": "high" }, { "addr": "929F", "scope": "abs", "name": "pendingOwnCmdCount", "meaning": "after the setup phase the game loop is run until it reaches 0 (all own setup commands sent)", "confidence": "high" }, { "addr": "92A1", "scope": "abs", "name": "messageState", "meaning": "persistent message id: $1A while player 1 waits for the map seed; cleared by the wait routines", "confidence": "high" }, { "addr": "92A8", "scope": "abs", "name": "recyclerCol", "meaning": "2 bytes: recycler column per side, $FF (none) at session start", "confidence": "high" }, { "addr": "92B4", "scope": "abs", "name": "comcenSpeedClass", "meaning": "2 bytes (own, opponent): setup value 0-3 translated through comcenSpeedClassMap at battle start", "confidence": "high" }, { "addr": "92D6", "scope": "abs", "name": "comcenStatusSnapshot", "meaning": "first byte (energy) preset to 50 at session start", "confidence": "medium" }, { "addr": "92E3", "scope": "abs", "name": "stepIntervalFrames", "meaning": "frames per engine step, 4 at game start", "confidence": "high" }, { "addr": "92EF", "scope": "abs", "name": "lastQuarterRound", "meaning": "preset to 50, then roundsByGameType/4 when the battle starts", "confidence": "high" }, { "addr": "92F3", "scope": "abs", "name": "setupPickupMode", "meaning": "cleared before the setup phase", "confidence": "high" }, { "addr": "92FC", "scope": "abs", "name": "revealAllUnitsFlag", "meaning": "copied from commBuildFlag: all units visible when playing the trainer", "confidence": "high" }, { "addr": "E01D", "scope": "abs", "name": "commExchangeState", "meaning": "bit 6 polled at $7B64: a complete packet from the opponent has arrived (both sides finished setup)", "confidence": "high" }, { "addr": "E03B", "scope": "abs", "name": "commLinkActive", "meaning": "nonzero = link already open (skip openCommLink)", "confidence": "high" }, { "addr": "F640", "scope": "abs", "name": "unitCol", "meaning": "unit column array; unitCol[0] = $FF marks 'unit template not yet loaded' for $ECF3", "confidence": "high" }, { "addr": "FFDE", "scope": "abs", "name": "ownerNames", "meaning": "20 bytes ($FFDE own name, $FFE8 opponent name) copied into the film header at +$11", "confidence": "high" } ], "dataBlocks": [ { "addr": "74EE", "length": 8, "type": "byteTable", "name": "neighbourOffsetTable", "description": "Cell offsets 0,1,2,$28,$2A,$50,$51,$52 of the eight neighbours of the centre cell (+$29) of a 3x3 block; indexed with a random 0-7 by the terrain fill pass at $74A7. The label L_74F3 in the middle comes from variant B." }, { "addr": "74F6", "length": 8, "type": "byteTable", "name": "bitMaskTable", "description": "$01,$02,$04,$08,$10,$20,$40,$80 - single-bit masks used by the terrain fill pass at $745B." }, { "addr": "763A", "length": 8, "type": "addrTable", "name": "riverStepLo", "description": "low bytes of the step routine for river segment types 0-7: $762E (right) for 0,2,4-7, $7636 (down) for 1, $7632 (up) for 3; high bytes in riverStepHi." }, { "addr": "7642", "length": 8, "type": "addrTable", "name": "riverStepHi", "description": "high bytes ($76) of the river step routine addresses." }, { "addr": "7894", "length": 20, "type": "byteTable", "name": "riverZoneOuterOffsets", "description": "Offsets of the 20 perimeter cells of a 6x6 block (column 0 rows 1-4, row 0 cols 0-5, column 5 rows 1-4, row 5 cols 0-5); the first four (same column, rows 1-4) are used alone near the map edges. Marked $80 by markRiverBanks. Labelled sub_7894 because variant B has code here." }, { "addr": "78A8", "length": 16, "type": "byteTable", "name": "riverZoneInnerOffsets", "description": "Offsets of the inner 4x4 cells (rows 1-4, cols 1-4) of the 6x6 block; marked $81 by markRiverBanks." }, { "addr": "78B8", "length": 7, "type": "byteTable", "name": "unitLayoutByGameType", "description": "Index (0-3) of the 400-byte unit start-position template at $F000 + 400*n used for game types 0-6: 2,2,2,1,3,0,0. Read by the comm tail routine $ECF3 (T34 build)." }, { "addr": "78BF", "length": 4, "type": "byteTable", "name": "comcenSpeedClassMap", "description": "Translates the comcen speed setting 0-3 into the engine speed class: 4,0,1,5 (applied to $92B4/$92B5 at battle start)." }, { "addr": "78C3", "length": 7, "type": "byteTable", "name": "roundsByGameType", "description": "Game length in rounds for game types 0-6: $3F,$3F,$1F,$7F,$7F,$FE,$FE (Scrimmage 63, QB Sneak 63, The Bomb 31, Face-Off 127, Sluggers 127, Full War 254, Defender 254). Read by sub_7D8B." }, { "addr": "78CA", "length": 7, "type": "byteTable", "name": "gameTypeOptionsByMenuItem", "description": "Default gameTypeOptions byte for the seven game-type menu items: $00,$01,$02,$13,$14,$1D,$1E (bits 3/4 = unit options and damage on for Face-Off, Sluggers, Full War, Defender). Read at $828F; labelled L_78CA/sub_78CB from variant B." } ], "misclassified": [ { "addr": "762E", "length": 12, "actual": "code", "evidence": "Three 4-byte stubs EE 0F 03 60 (INC $030F RTS), CE 10 03 60 (DEC $0310 RTS), EE 10 03 60 (INC $0310 RTS) reached through the JMP at $762B whose operand is patched from riverStepLo/Hi." } ], "insights": [ "The battlefield is NOT loaded from disk: generateBattlefieldMap ($6F04, overlay A) clears $F000-$F63F to $40 ($EC00) and builds the terrain procedurally from the 24-bit mapSeed ($0B95-$0B97) using the scenario RNG $5A-$5C (nextScenarioRandom $FD4A). Track 18 s8-14 ($F000-$F6FF, the 'map' in docs/overview.md) actually holds four 400-byte unit start-position templates (unitCol[100], unitRow[100], flags[100], type[100]) selected by unitLayoutByGameType ($78B8) and copied to $F640 by the comm-tail routine $ECF3 before the map is generated.", "Map names: a map is identified by a 5-letter name over the 32-symbol alphabet at $890C (space, A-Z, + - * / =); the name is the seed packed 5 bits per letter (last letter in the low bits of $5A/$0B95), so seed and name are the same data (seedToMapName $7850 / enterMapName $7717). A low-5-bit value of $1F (name ending in '=') marks a symmetric map: the river runs along row 20 and after generation $EC54 overwrites the bottom half with the 180-degree rotation of the top half (tile codes rotated through the table at $EC3B). F7 = random name, F5 = symmetric random (ORs $1F), F1 = flip (mapFlipFlag, $EC58 rotates the whole map).", "Map generation order in sub_6F04: clear, seed RNG, name, generateRiver ($7548, river tiles $55-$5C = terrain class 1, cost 65, cyan), sub_6F9E/sub_70D9 feature passes, clearMapMarkers($80), terrain fill $65 (sub_73EE), sub_710A, clearMapMarkers($81), terrain fill $61, sub_7340, then mirror for symmetric maps. markRiverBanks reserves a 4x4 (aligned to even coordinates) zone around every river cell with $81 and a 1-cell ring with $80 so the fill passes keep the banks clear; the markers look like units (bit 7) until clearMapMarkers erases them. River tiles: $55 bend down/right, $56 left/down, $57 up/right, $58 left/up, $59-$5C horizontal variants; vertical runs are always a single cell (a bend follows immediately). On symmetric maps a 1/8 chance leaves the river on row 20 where the mirror erases it - effectively the same 1/8 'no river' chance that asymmetric maps get explicitly.", "Each machine keeps the map and the unit arrays in its own orientation: startGameSession rotates the map by 180 degrees when playerSide XOR mapFlipFlag is 1 ($EC58) and mirrors all unit coordinates (39 - v via $3FA3, arrays $F640/$F6A4/$F7D0/$F834/$F898/$F8FC) for player 1 ($EC91), so every player's own units start in the bottom half (rows 20-39). This is why received commands have their x/y mirrored and why the own-side test is a self-modified BCC/BCS after CMP #$32 (patchOwnSideBranches $7BC6 lists all 11 patch sites).", "Game start protocol (startGameSession $78D1): side chosen or negotiated -> command $9D (sendChooseSide) -> player 0 sends the map seed as command $8B (or $A6 = flipped; handler $51EA stores mapSeed and clears gamePhase bit 7) -> identity/version exchange $ED43 (result in $90F8 must be $DB) -> unit template copy -> film header written at $D000 (+4 trainer flag, +5 side, +6..+$B map name, +$C gameTypeOptions, +$11..+$24 owner names, +$2B..+$2D seed, +$2E firstPlay<<1 | flip; the film replay start $7CD1 reads the same offsets) -> map generation -> optional unit setup phase (all types except Scrimmage and The Bomb, gamePhase $3F) -> wait for $E01D bit 6 -> clock from roundsByGameType, comcen speed classes, film snapshot, recyclers, shuffled unit order -> JMP $EE26 in the comm tail. Phase transitions are synchronised with the 1-byte command $9E (sendPhaseSyncAndWait $7BF9, handler $520C clears gamePhase bits 0-1).", "Comm build 1 (track 35, commBuildFlag $0BA5 bit 7) is the TRAINER: the opponent name is the fixed string 'TRAINER' at $884E, revealAllUnitsFlag is set from the flag, and at game start overlay A loads the track-29 sub-overlay to $EC00, runs its entry $EC0F and then restores the track-34 tail ($1070).", "enterMapName contains a debug trap: if a character in mapName is not found in the alphabet it executes BRK ($780D); the BRK vector path in the IRQ handler ($10FF -> irqExit) makes this a no-op in the release build.", "The XREF merges both overlay variants. In variant A the following labels inside this chunk are NOT routine entries or branch targets (they come from variant B code at the same addresses): sub_7552, D_754D, D_754F, L_7557, D_7586, L_7587, sub_7688, D_7702, sub_7879, D_7891, D_7893, sub_7894, sub_78CB, sub_78E7, D_7979, sub_7987, sub_79A1, sub_79D8, sub_7A32, sub_7A78, sub_7ABA, sub_7AEC, sub_7B44, sub_7BA2, D_77E0, D_782B. Callers seen for this chunk in variant A: sub_74FE from $6F47/$6F5F, sub_7548 from $6F39, sub_7850 from $6F36, sub_7872 from $72A4/$74E7, L_76AD from $8339, sub_7717/sub_7836 also from the copy protection at $09F6/$09F9, sub_78D1 from $0B40, sub_7BC6 from $7D20.", "Variables at $030F-$0313 and $032D/$032E live inside the setup block ($0200-$03FF loaded from track 18 s15-16) and are used as map-generator scratch at run time; they are not settings." ] }