{
 "chunk": "game_ovl_6F00_A_6F00",
 "unit": "game/ovl_6F00_A",
 "routines": [
  {
   "addr": "6F04",
   "name": "generateBattlefieldMap",
   "summary": "Entry point of the random battlefield generator (variant A of the $6F00 overlay). Derives the map-style flags at $032E from the game type in $0BA3 bits 0-2 (type 1 QB SNEAK -> $80, type 6 DEFENDER -> $40, else 0), sets $032D bit 7 when the low 5 bits of the map seed $0B95 are all set (= map code ending in '=', produces a point-symmetric map), shows status message 1 (sub_C853), blanks the 40x40 map at $F000 to $40 (sub_EC00), copies the 3-byte map seed $0B95-$0B97 into the scenario RNG state $5A-$5C (sub_EC2B) and falls into runMapGeneratorPasses. Called by manualLookupCheck ($09E0, to show the map the player must identify), by the map/setup screens of overlay A ($76AD, $76FF, $7A02, $7D23).",
   "inputs": "$0BA3 bits 0-2 game type, $0B95-$0B97 map seed, $EC00 sub-overlay (T34 s12-15) present, contour rule table at $0200 (T18 s15) present",
   "outputs": "$032D, $032E; map at $F000-$F63F filled; $5A-$5C RNG state; $0BA9-$0BAD map-id text; $0300-$032C generator parameters; clobbers $0400-$04EF and $9025-$9028",
   "confidence": "high"
  },
  {
   "addr": "6F36",
   "name": "runMapGeneratorPasses",
   "summary": "Body of the map generator (fall-through from generateBattlefieldMap; the JSR at $7125 listed in XREF belongs to variant B). Pass order: encode the seed in A/Y/X as the 5-character map id at $0BA9 (sub_7850); carve a river (sub_7548, tiles $55-$5C, marks a keep-clear zone with $80/$81); pick hill summit sites (pickHillSummitSites); paint the summits (paintHillSummits, tile $65); turn $80 markers back into $40 (sub_74FE); applyContourTiles($65) with $0324=0 draws the upper-slope contour $41-$54; grow lower hills $61 beside the slopes (growLowerHillsBesideSlopes); clear $81 markers; applyContourTiles($61) with $0324=$20 draws the lower contour $21-$34; place forest clusters (placeForestClusters, tiles $5D-$60). For symmetric maps ($032D bit 7) the top half is then mirrored onto the bottom half (sub_EC54) and, if the river crossed the centre ($032D bit 6), cells (19,19)/(19,20)/(20,20) are patched to $55/$58/$40 so the two river halves join. Ends with jmp sub_C6EF (clear the status message). serviceLinkWhileGenerating is called between passes.",
   "inputs": "A/Y/X = map seed bytes (from sub_EC2B), $032D, $032E, RNG $5A-$5C",
   "outputs": "battlefield map $F000-$F63F, $0BA9-$0BAD map id text, $0300-$032C, map cells $F30B/$F333/$F334",
   "confidence": "high"
  },
  {
   "addr": "6F91",
   "name": "serviceLinkWhileGenerating",
   "summary": "Keeps the two-player link alive during the long map generation: unless $0B9B bit 7 (solo/film mode) is set, and if zp_B1 bit 7 (non-battle phase flag) is set, jumps to serviceCommandExchange ($4DA9). Otherwise returns immediately. Called four times from runMapGeneratorPasses and from $773A.",
   "inputs": "$0B9B bit 7, zp_B1 bit 7",
   "outputs": "side effects of sub_4DA9 (message system / command exchange)",
   "confidence": "medium"
  },
  {
   "addr": "6F9E",
   "name": "pickHillSummitSites",
   "summary": "Chooses how many hill summits to place in the top half of the map and where. Count = (rnd&3)+(rnd&1), re-rolled to 1..4 if it came out 1, forced to 4 for map style $40 (DEFENDER) and masked to 0/1 for map style $80 (QB SNEAK); stored in $0300. For each site a blob size 2..5 goes to $0307[i] (mirrored into $0307[count+i]) and four candidate rows 7..14 go to $0325/$031C[0..3]. findBlankSitesOnLanes then resolves the sites; afterwards $0300 is doubled, the candidate rows are mirrored to 39-row (sub_3FA3) and findBlankSitesOnLanes runs again for the bottom half, so every summit has a point-mirrored twin. Exits early (count 0) via L_7088 with $0300 = 0.",
   "inputs": "$032E bits 6/7, RNG",
   "outputs": "$0300 total site count, $0307[0..7] sizes, $0314[i] columns, $031C[i] rows, $0325[0..3] rows, $0329[0..3] lane order; zp_28-zp_2B, zp_18",
   "confidence": "medium"
  },
  {
   "addr": "701C",
   "name": "findBlankSitesOnLanes",
   "summary": "Resolves site positions for entries zp_2A .. $0300-1: shuffles the four lane columns (shuffleLaneColumns) and, for each site, takes the next lane (zp_2B cycling 3..0) and the next candidate row (zp_29 cycling zp_28..0), tries up to 20 jittered columns (addRandomJitter, +/-6, must be < 40) until the map cell is blank ($40); if a row yields nothing it tries the other candidate rows and finally drops the site (dec $0300). Column goes to $0314[i], row to $031C[i]. Also entered by fall-through for the mirrored second pass. On exit $0300 = number of sites actually placed (zp_2A).",
   "inputs": "zp_2A first site index, $0300 requested count, zp_28 last row-candidate index, $0325[] candidate rows",
   "outputs": "$0314[]/$031C[] site coordinates, $0300 corrected count, $0329[] shuffled lanes, zp_1D/zp_1E/zp_1F/zp_29/zp_2B",
   "confidence": "medium"
  },
  {
   "addr": "708E",
   "name": "advanceLaneIndex",
   "summary": "zp_2B = zp_2B - 1, wrapping from 0 back to 3 (cycles through the four lane columns in $0329).",
   "inputs": "zp_2B",
   "outputs": "zp_2B",
   "confidence": "high"
  },
  {
   "addr": "7097",
   "name": "advanceRowCandidate",
   "summary": "zp_29 = zp_29 - 1, wrapping from 0 back to zp_28 (cycles through the candidate rows in $0325).",
   "inputs": "zp_29, zp_28",
   "outputs": "zp_29",
   "confidence": "high"
  },
  {
   "addr": "70A0",
   "name": "shuffleLaneColumns",
   "summary": "Builds a random permutation of 0..3 in $9025-$9028 (identity, then four random swaps) and stores laneColumnTable[perm[y]] (6, 15, 25, 34) into $0329-$032C so hills are spread over the four vertical lanes of the map in random order.",
   "inputs": "RNG, laneColumnTable $6F00",
   "outputs": "$0329-$032C, $9025-$9028 clobbered, zp_18",
   "confidence": "high"
  },
  {
   "addr": "70D9",
   "name": "paintHillSummits",
   "summary": "For every site i < $0300 grows a blob of $0307[i] (2..5) cells of summit tile $65 (random variant $65/$66) at ($0314[i], $031C[i]) via growTerrainBlob with horizontal spread $C8/256 and vertical spread $20/256, $0304 = 0 (no 2x2 constraint). Does nothing when $0300 is 0.",
   "inputs": "$0300, $0307[], $0314[], $031C[]",
   "outputs": "map cells; $0301 = $65, $0304 = 0, zp_2A, zp_A7/zp_A8",
   "confidence": "high"
  },
  {
   "addr": "710A",
   "name": "growLowerHillsBesideSlopes",
   "summary": "Scans every interior cell (x,y in 1..38). For a blank cell whose N, W, E or S neighbour is an upper-slope contour tile (terrain class 5, tiles $41-$54) it grows, with probability 1/4 (N,S) or 1/2 (W,E) per neighbour, a 5-cell blob of lower-hill tile $61 ($61-$64 variants) from that cell (growTerrainBlob, spreads $40/$10). This widens each summit into a hill body before the second contour pass. Skips entirely when $0300 is 0 (no hills).",
   "inputs": "$0300, map, terrainClassTable $077E, RNG",
   "outputs": "map cells; $0301 = $61, $0304 = 0, zp_A7/zp_A8 scan position",
   "confidence": "medium"
  },
  {
   "addr": "71B0",
   "name": "growTerrainBlob",
   "summary": "Generic random blob painter. Starting at (zp_A7, zp_A8) it paints blobTile $0301 (randomised through sub_7872) on up to A blank ($40) cells using a depth-first walk with an explicit stack at $0400 (col) / $0450 (row) / $04A0 (direction flags), max 80 entries. From each cell it tries N, W, E, S once each (flag bits 0-3 of zp_6B), accepting a neighbour when it is inside the map, blank and a random byte is below $91DB (vertical) or $91DA (horizontal); a new cell never grows back toward the origin row/column (bits preset from its position relative to zp_A7/zp_A8). With $0304 bit 6 set a cell is only painted if checkAlignedBlockForForest allows it. Returns when A cells are placed, the stack is exhausted or full. Note: overwrites $0400-$04EF, the page that also holds the copy-protection answer table.",
   "inputs": "A = cells to place, X = horizontal spread (0-255), Y = vertical spread, zp_A7/zp_A8 origin, $0301 tile, $0304 flags",
   "outputs": "map cells; $91DA/$91DB, zp_18, zp_1A, zp_6B, zp_A5/zp_A6, zp_B2 stack pointer, zp_B3 placed count, $0400-$04EF",
   "confidence": "high"
  },
  {
   "addr": "72F6",
   "name": "checkAlignedBlockForForest",
   "summary": "For the blob cell (zp_A5, zp_A6) looks at the 2x2-aligned block containing it (coordinates with bit 0 cleared) and returns C = 0 only if all four cells are terrain class 0 (blank) or 2 (forest $5D-$60); otherwise C = 1. On success zp_48/49 are re-pointed at the original cell. Forest blobs are therefore confined to aligned 2x2 blocks that contain nothing but blank/forest.",
   "inputs": "zp_A5/zp_A6, map, terrainClassTable",
   "outputs": "C flag, zp_48/49, X/Y clobbered",
   "confidence": "medium"
  },
  {
   "addr": "7340",
   "name": "placeForestClusters",
   "summary": "Scatters forest ($0301 = $5D, variants $5D-$60, $0304 = $40 so growth respects 2x2 alignment). Cluster count-1 = (rnd&7)+2 normally (1/32 chance of no forest at all when rnd&$1F = 0), fixed 9 for map style $80 (QB SNEAK), and additionally masked with 3 for style $40 (DEFENDER). Each cluster picks a centre (rnd&$1F)+4 in both axes and makes 6 seeds jittered around it (addRandomJitter +/-8 columns, +/-4 rows, retried while >= 40), each seed painted by paintForestClusterSeeds. Also stores $41 into $0302, which nothing reads (dead store).",
   "inputs": "$032E, RNG",
   "outputs": "map cells; $0301, $0302, $0304, zp_2A cluster counter, zp_2B seed counter, zp_92-zp_95",
   "confidence": "medium"
  },
  {
   "addr": "73AD",
   "name": "paintForestClusterSeeds",
   "summary": "Paints a 4x2 block of forest blob seeds around (zp_93, zp_92): calls paintForestBlobAt at (x,y), (x+1,y), (x+1,y+1), (x,y+1), (x-2,y), (x-1,y), (x-1,y+1) and falls into it for (x-2,y+1), stepping zp_92/zp_93 in between.",
   "inputs": "zp_93 column, zp_92 row",
   "outputs": "map cells; zp_92/zp_93 restored to start values; zp_A7/zp_A8",
   "confidence": "medium"
  },
  {
   "addr": "73D4",
   "name": "paintForestBlobAt",
   "summary": "If zp_93 (column) and zp_92 (row) are both < 40, copies them to zp_A7/zp_A8 and grows a 4-cell blob with growTerrainBlob (spreads $DC/$DC, tile $0301, flags $0304). Out-of-range coordinates are ignored.",
   "inputs": "zp_92/zp_93, $0301/$0304",
   "outputs": "map cells; zp_A7/zp_A8",
   "confidence": "medium"
  },
  {
   "addr": "73EE",
   "name": "applyContourTiles",
   "summary": "Contour/edge pass for the hill terrain whose interior tile is A ($65 summit or $61 lower hill). zp_8E = terrain class of A, zp_91 = zp_8E-1 = class of its contour ring. Scans all 1600 cells: a cell of class zp_91 is erased to $40 and re-evaluated; cells of class >= zp_8E and river cells (class 1) are left alone; every other cell goes through applyContourRuleAtCell, which may paint a contour tile (rule value - $0324), fill the cell with the interior tile, or erase a neighbour; when it returns C = 1 the scan backs up two rows/columns and continues. Called with A = $65 ($0324 = 0 -> contour $41-$54) and A = $61 ($0324 = $20 -> contour $21-$34).",
   "inputs": "A = interior tile, $0324 contour offset, contour rule table $0200, terrainClassTable, map",
   "outputs": "map cells; $0312 = interior tile, zp_8E, zp_91, zp_2D/zp_2E scan row/col, zp_8C/zp_8D, zp_21",
   "confidence": "medium"
  },
  {
   "addr": "7444",
   "name": "applyContourRuleAtCell",
   "summary": "Builds an 8-bit mask (zp_21) of the neighbours of cell (zp_2E, zp_2D) whose terrain class is >= zp_8E (bit order NW,N,NE,W,E,SW,S,SE from neighbourOffsetTable/neighbourBitTable), clears bits that point off the map, and looks the mask up in the contour rule table at $0200: 0 = leave the cell; $01-$7F = paint tile (value - $0324) with a random variant (sub_7872) at the cell, C = 0; $FF = fill the cell with the interior tile $0312 and return C = 1 (rescan from two cells back); $80-$FE = irregular pattern: with a 50% random bit (taken from zp_8D, refilled every 7 calls) erase the neighbour indexed by the low 3 bits if it is of class zp_8E (C = 1), otherwise fill the cell when the value is >= $90 (C = 1) or leave it (C = 0). On C = 1 zp_2E/zp_2D are decremented by 2 (floor 0).",
   "inputs": "zp_2E/zp_2D cell, zp_48 pointer to (x-1,y-1), zp_8E, $0200 table, $0324, $0312",
   "outputs": "C flag, map cells, zp_2D/zp_2E, zp_21, zp_18, zp_8C/zp_8D",
   "confidence": "medium"
  }
 ],
 "variables": [
  {
   "addr": "18",
   "scope": "zp",
   "name": "blobCellsToPlace",
   "meaning": "in this chunk: number of cells growTerrainBlob still has to paint (also scratch in pickHillSummitSites and applyContourRuleAtCell)",
   "confidence": "high"
  },
  {
   "addr": "1A",
   "scope": "zp",
   "name": "blobBackDirectionBits",
   "meaning": "direction bits (1 N, 2 W, 4 E, 8 S) preset on a newly pushed blob cell so it does not grow back toward the origin",
   "confidence": "high"
  },
  {
   "addr": "1D",
   "scope": "zp",
   "name": "siteCandidateRow",
   "meaning": "row currently being tried for a hill site",
   "confidence": "high"
  },
  {
   "addr": "1E",
   "scope": "zp",
   "name": "siteRetryCount",
   "meaning": "remaining column tries (20) for the current site row",
   "confidence": "high"
  },
  {
   "addr": "1F",
   "scope": "zp",
   "name": "siteStartRowIndex",
   "meaning": "row-candidate index at which the search for the current site started (detects a full cycle)",
   "confidence": "high"
  },
  {
   "addr": "21",
   "scope": "zp",
   "name": "neighbourMask",
   "meaning": "8-bit mask of surrounding cells that belong to the terrain being contoured (index into the $0200 rule table)",
   "confidence": "high"
  },
  {
   "addr": "28",
   "scope": "zp",
   "name": "lastRowCandidateIndex",
   "meaning": "site count - 1; wrap value for advanceRowCandidate",
   "confidence": "high"
  },
  {
   "addr": "29",
   "scope": "zp",
   "name": "rowCandidateIndex",
   "meaning": "index into the candidate row table $0325",
   "confidence": "high"
  },
  {
   "addr": "2A",
   "scope": "zp",
   "name": "mapGenItemIndex",
   "meaning": "current hill site index (pickHillSummitSites/paintHillSummits) or remaining forest cluster count (placeForestClusters)",
   "confidence": "high"
  },
  {
   "addr": "2B",
   "scope": "zp",
   "name": "laneIndex",
   "meaning": "index into the shuffled lane column table $0329 (3..0); in placeForestClusters the remaining seeds per cluster",
   "confidence": "high"
  },
  {
   "addr": "2D",
   "scope": "zp",
   "name": "contourScanRow",
   "meaning": "row of the cell being examined by applyContourTiles",
   "confidence": "high"
  },
  {
   "addr": "2E",
   "scope": "zp",
   "name": "contourScanCol",
   "meaning": "column of the cell being examined by applyContourTiles",
   "confidence": "high"
  },
  {
   "addr": "48",
   "scope": "zp",
   "name": "mapCellPtr",
   "meaning": "pointer into the $F000 map set by sub_25FA/sub_25C7; the generator addresses neighbours with offsets 0/1/2/$28/$2A/$50/$51/$52 from (x-1,y-1)",
   "confidence": "high"
  },
  {
   "addr": "49",
   "scope": "zp",
   "name": "mapCellPtrHi",
   "meaning": "high byte of mapCellPtr",
   "confidence": "high"
  },
  {
   "addr": "5A",
   "scope": "zp",
   "name": "scenarioSeed0",
   "meaning": "scenario RNG state byte 0; loaded from mapSeed $0B95 by sub_EC2B at the start of generation",
   "confidence": "high"
  },
  {
   "addr": "6B",
   "scope": "zp",
   "name": "blobDirectionFlags",
   "meaning": "growTerrainBlob: bits 0-3 = N/W/E/S already tried for the current stack cell, bit 6 = 2x2 alignment check required (from $0304)",
   "confidence": "high"
  },
  {
   "addr": "8C",
   "scope": "zp",
   "name": "randomBitsLeft",
   "meaning": "applyContourRuleAtCell: uses left in the random bit buffer zp_8D before a new random byte is drawn",
   "confidence": "high"
  },
  {
   "addr": "8D",
   "scope": "zp",
   "name": "randomBitBuffer",
   "meaning": "random byte shifted right one bit per irregular-pattern decision",
   "confidence": "high"
  },
  {
   "addr": "8E",
   "scope": "zp",
   "name": "contourInteriorClass",
   "meaning": "terrain class of the interior tile being contoured (6 for $65, 4 for $61); neighbours of class >= this count as inside",
   "confidence": "high"
  },
  {
   "addr": "91",
   "scope": "zp",
   "name": "contourRingClass",
   "meaning": "contourInteriorClass - 1 = class of the contour tiles that get erased and redrawn",
   "confidence": "high"
  },
  {
   "addr": "92",
   "scope": "zp",
   "name": "forestSeedRow",
   "meaning": "row of the current forest seed (jittered from zp_94)",
   "confidence": "medium"
  },
  {
   "addr": "93",
   "scope": "zp",
   "name": "forestSeedCol",
   "meaning": "column of the current forest seed (jittered from zp_95)",
   "confidence": "medium"
  },
  {
   "addr": "94",
   "scope": "zp",
   "name": "forestClusterRow",
   "meaning": "row of the current forest cluster centre (4..35)",
   "confidence": "medium"
  },
  {
   "addr": "95",
   "scope": "zp",
   "name": "forestClusterCol",
   "meaning": "column of the current forest cluster centre (4..35)",
   "confidence": "medium"
  },
  {
   "addr": "A5",
   "scope": "zp",
   "name": "blobCurCol",
   "meaning": "column of the blob cell being expanded (reuses the view-origin column variable); input to sub_25C7",
   "confidence": "high"
  },
  {
   "addr": "A6",
   "scope": "zp",
   "name": "blobCurRow",
   "meaning": "row of the blob cell being expanded",
   "confidence": "high"
  },
  {
   "addr": "A7",
   "scope": "zp",
   "name": "blobOriginCol",
   "meaning": "column where the current blob starts; also the scan column in growLowerHillsBesideSlopes",
   "confidence": "high"
  },
  {
   "addr": "A8",
   "scope": "zp",
   "name": "blobOriginRow",
   "meaning": "row where the current blob starts; also the scan row in growLowerHillsBesideSlopes",
   "confidence": "high"
  },
  {
   "addr": "B1",
   "scope": "zp",
   "name": "gamePhase",
   "meaning": "0 during battle, nonzero in setup/menu phases; bit 7 set lets serviceLinkWhileGenerating call the command exchange",
   "confidence": "medium"
  },
  {
   "addr": "B2",
   "scope": "zp",
   "name": "blobStackPtr",
   "meaning": "growTerrainBlob stack index into $0400/$0450/$04A0 (max $50 entries)",
   "confidence": "high"
  },
  {
   "addr": "B3",
   "scope": "zp",
   "name": "blobCellsPlaced",
   "meaning": "cells painted so far by growTerrainBlob",
   "confidence": "high"
  },
  {
   "addr": "0200",
   "scope": "abs",
   "name": "contourRuleTable",
   "meaning": "256-byte table indexed by neighbourMask: 0 = nothing, $01-$7F = contour tile to place (minus $0324), $80-$8F = randomly erase the neighbour in bits 0-2, $90-$FE = erase that neighbour or else fill, $FF = fill with the interior tile. Loaded from track 18 sector 15 by sub_10BB; the page is also used as BAM buffer and sprite shape buffer elsewhere",
   "confidence": "medium"
  },
  {
   "addr": "0300",
   "scope": "abs",
   "name": "hillSiteCount",
   "meaning": "number of hill summit sites (both halves); 0 = no hills",
   "confidence": "high"
  },
  {
   "addr": "0301",
   "scope": "abs",
   "name": "blobTile",
   "meaning": "base tile code painted by growTerrainBlob ($65 summit, $61 lower hill, $5D forest); randomised by sub_7872 using tileVariantMaskTable",
   "confidence": "high"
  },
  {
   "addr": "0302",
   "scope": "abs",
   "name": "unusedBlobTile0302",
   "meaning": "written with $41 by placeForestClusters, never read (dead store)",
   "confidence": "high"
  },
  {
   "addr": "0304",
   "scope": "abs",
   "name": "blobFlags",
   "meaning": "OR-ed into each blob cell's direction flags; bit 6 ($40) = only paint where the aligned 2x2 block is blank/forest",
   "confidence": "high"
  },
  {
   "addr": "0307",
   "scope": "abs",
   "name": "hillSizeTable",
   "meaning": "8 bytes: blob size (2..5) of each hill summit; entries count+i mirror entries i",
   "confidence": "high"
  },
  {
   "addr": "030F",
   "scope": "abs",
   "name": "riverCol",
   "meaning": "river generator (sub_7548): current column",
   "confidence": "medium"
  },
  {
   "addr": "0310",
   "scope": "abs",
   "name": "riverRow",
   "meaning": "river generator: current row",
   "confidence": "medium"
  },
  {
   "addr": "0311",
   "scope": "abs",
   "name": "riverDirection",
   "meaning": "river generator: direction code of the current segment (tile = $55 + code)",
   "confidence": "medium"
  },
  {
   "addr": "0312",
   "scope": "abs",
   "name": "contourInteriorTile",
   "meaning": "interior tile of the current applyContourTiles pass (also reused by the river generator as the next direction code)",
   "confidence": "high"
  },
  {
   "addr": "0314",
   "scope": "abs",
   "name": "hillSiteColTable",
   "meaning": "8 bytes: column of each hill summit site",
   "confidence": "high"
  },
  {
   "addr": "031C",
   "scope": "abs",
   "name": "hillSiteRowTable",
   "meaning": "8 bytes: row of each hill summit site (initially a copy of the candidate rows)",
   "confidence": "high"
  },
  {
   "addr": "0324",
   "scope": "abs",
   "name": "contourTileOffset",
   "meaning": "subtracted from contourRuleTable values: 0 for the summit pass (contour $41-$54), $20 for the lower-hill pass (contour $21-$34)",
   "confidence": "high"
  },
  {
   "addr": "0325",
   "scope": "abs",
   "name": "siteCandidateRowTable",
   "meaning": "4 candidate rows (7..14 for the top half, 39-row for the mirrored bottom half)",
   "confidence": "high"
  },
  {
   "addr": "0329",
   "scope": "abs",
   "name": "laneColumnOrder",
   "meaning": "4 lane columns (6,15,25,34) in random order",
   "confidence": "high"
  },
  {
   "addr": "032D",
   "scope": "abs",
   "name": "symmetricMapFlags",
   "meaning": "bit 7 = map seed low 5 bits all set (map code ends in '='): generate a point-symmetric map; bit 6 = river crossed the centre (set by sub_759E), requires the centre patch",
   "confidence": "medium"
  },
  {
   "addr": "032E",
   "scope": "abs",
   "name": "mapStyleFlags",
   "meaning": "from the game type: $80 for QB SNEAK (no river, at most one hill, many forests), $40 for DEFENDER (always a river, four hills, few forests), 0 otherwise",
   "confidence": "high"
  },
  {
   "addr": "0400",
   "scope": "abs",
   "name": "blobStackCol",
   "meaning": "80-entry column stack of growTerrainBlob (overwrites the copy-protection answer table)",
   "confidence": "high"
  },
  {
   "addr": "0450",
   "scope": "abs",
   "name": "blobStackRow",
   "meaning": "80-entry row stack of growTerrainBlob",
   "confidence": "high"
  },
  {
   "addr": "04A0",
   "scope": "abs",
   "name": "blobStackFlags",
   "meaning": "80-entry direction-flag stack of growTerrainBlob",
   "confidence": "high"
  },
  {
   "addr": "0770",
   "scope": "abs",
   "name": "tileVariantMaskTable",
   "meaning": "indexed by tile - $40 (tiles below $40 use tile - $20): mask AND-ed with a random byte and added to the base tile by sub_7872 (e.g. $65 -> 1, $61/$5D/$55/$59 -> 3)",
   "confidence": "medium"
  },
  {
   "addr": "077E",
   "scope": "abs",
   "name": "terrainClassTable",
   "meaning": "map cell value -> terrain class: $40 -> 0 plain, $21-$34 -> 3 lower contour, $41-$54 -> 5 upper contour, $55-$5C -> 1 river, $5D-$60 -> 2 forest, $61-$64 -> 4 lower hill, $65-$66 -> 6 summit, $67 -> 7 recycler; classes 3-6 have heights 1-4 in $38B3",
   "confidence": "high"
  },
  {
   "addr": "0B95",
   "scope": "abs",
   "name": "mapSeed",
   "meaning": "3-byte map seed ($0B95-$0B97) copied to the scenario RNG before generation; its low 5 bits select the symmetric-map mode when all set",
   "confidence": "high"
  },
  {
   "addr": "0B9B",
   "scope": "abs",
   "name": "soloModeFlag",
   "meaning": "bit 7 = solo/film mode: serviceLinkWhileGenerating does nothing",
   "confidence": "medium"
  },
  {
   "addr": "0BA3",
   "scope": "abs",
   "name": "gameTypeOptions",
   "meaning": "bits 0-2 game type; types 1 (QB SNEAK) and 6 (DEFENDER) select special map styles",
   "confidence": "high"
  },
  {
   "addr": "0BA9",
   "scope": "abs",
   "name": "mapIdText",
   "meaning": "5-character base-32 map id ($0BA9-$0BAD, alphabet at $890C) written from the seed by sub_7850 at the start of generation",
   "confidence": "high"
  },
  {
   "addr": "9025",
   "scope": "abs",
   "name": "lanePermutation",
   "meaning": "4 bytes of the sprite build buffer reused by shuffleLaneColumns as a permutation of 0..3",
   "confidence": "high"
  },
  {
   "addr": "91DA",
   "scope": "abs",
   "name": "blobSpreadH",
   "meaning": "growTerrainBlob: probability (x/256) of growing west/east (reuses currentUnitCol)",
   "confidence": "high"
  },
  {
   "addr": "91DB",
   "scope": "abs",
   "name": "blobSpreadV",
   "meaning": "growTerrainBlob: probability (x/256) of growing north/south (reuses currentUnitRow)",
   "confidence": "high"
  },
  {
   "addr": "F30B",
   "scope": "abs",
   "name": "mapCell19x19",
   "meaning": "map cell column 19 row 19; set to river tile $55 when a symmetric map's river crosses the centre",
   "confidence": "medium"
  },
  {
   "addr": "F333",
   "scope": "abs",
   "name": "mapCell19x20",
   "meaning": "map cell column 19 row 20; set to river tile $58 for the centre patch",
   "confidence": "medium"
  },
  {
   "addr": "F334",
   "scope": "abs",
   "name": "mapCell20x20",
   "meaning": "map cell column 20 row 20; blanked to $40 for the centre patch",
   "confidence": "medium"
  }
 ],
 "dataBlocks": [
  {
   "addr": "6F00",
   "length": 4,
   "type": "byteTable",
   "name": "laneColumnTable",
   "description": "$06,$0F,$19,$22: the four lane columns (6, 15, 25, 34) along which hill summits are placed; shuffled into $0329 by shuffleLaneColumns. Labelled sub_6F00 only because variant B has code here."
  },
  {
   "addr": "71AB",
   "length": 5,
   "type": "byteTable",
   "name": "unusedBitTable71AB",
   "description": "$08,$04,$02,$01,$00 - not referenced by any code in variant A (the $71AC reference in XREF is variant B code)."
  },
  {
   "addr": "74EE",
   "length": 8,
   "type": "byteTable",
   "name": "neighbourOffsetTable",
   "description": "$00,$01,$02,$28,$2A,$50,$51,$52: offsets of the 8 neighbours (NW,N,NE,W,E,SW,S,SE) from a pointer at (x-1,y-1). The disassembler split it at L_74F3 because of a variant-B branch target; it continues past the chunk end."
  },
  {
   "addr": "74F6",
   "length": 8,
   "type": "byteTable",
   "name": "neighbourBitTable",
   "description": "$01..$80: bit for each neighbour in neighbourMask (just past the chunk end, used by applyContourRuleAtCell)."
  }
 ],
 "misclassified": [
  {
   "addr": "6F00",
   "length": 4,
   "actual": "data",
   "evidence": "labelled sub_6F00 but it is the 4-byte lane column table read by LDA sub_6F00,X at $70CF; the JSR $6F00 at $7122 exists only in overlay variant B."
  },
  {
   "addr": "71AB",
   "length": 5,
   "actual": "data",
   "evidence": "bytes between the RTS at $71AA and sub_71B0, unreferenced in variant A (correctly emitted as .byte)."
  }
 ],
 "insights": [
  "Overlay A $6F00-$74ED is the random battlefield generator. Pipeline (runMapGeneratorPasses): seed -> 5-char map id; river ($55-$5C) with $80/$81 keep-clear markers; hill summits ($65/$66) on 4 lanes with point-mirrored twins; upper contour ring ($41-$54) from the $0200 rule table; lower hills ($61-$64) grown beside the ring; lower contour ring ($21-$34); forest clusters ($5D-$60) in 2x2-aligned blocks; optional point mirroring of the whole map (sub_EC54) when the seed's low 5 bits are $1F (map code ends in '=').",
  "Terrain classes (table $077E) line up with the hill model: class 0 plain ($40), 3 lower contour ($21-$34, height 1), 4 lower hill ($61-$64, height 2), 5 upper contour ($41-$54, height 3), 6 summit ($65-$66, height 4), 1 river ($55-$5C, 8 directional pieces), 2 forest ($5D-$60), 7 recycler ($67). Colour table $07EA gives class 1 cyan, class 2 green, classes 3-6 orange, class 0 yellow.",
  "The 256-byte table at $0200 (track 18 sector 15, loaded by sub_10BB) is a neighbourhood -> tile rule table for drawing contour rings: bit i set = neighbour i (NW,N,NE,W,E,SW,S,SE) is higher terrain; entries are a contour tile, $FF = fill the cell, or $80+n = randomly delete neighbour n (>= $90 also fills). The same page is reused as BAM buffer and sprite shape buffer, so it must be intact when generateBattlefieldMap runs.",
  "Map cell values with bit 7 set are treated as occupied everywhere in the generator; the river generator uses $80 (wide zone) and $81 (near ring) as temporary keep-clear markers that sub_74FE converts back to $40 after the summit pass and after the lower-hill pass respectively.",
  "Game type affects the map: $0BA3 & 7 = 1 (QB SNEAK) -> $032E = $80: no river, 0-1 hill, 10 forest clusters; = 6 (DEFENDER) -> $032E = $40: river always, 4 hills, 1-4 forest clusters; other types: river 7/8 of the time, 0-4 hills, 3-10 clusters (1/32 chance of none).",
  "growTerrainBlob ($71B0) is a reusable DFS blob painter with its stack in $0400/$0450/$04A0 - the copy-protection answer table at $0401 is destroyed by it, which is harmless because manualLookupCheck reads its answer before calling generateBattlefieldMap and the check is disabled anyway.",
  "Several sub_ labels inside this chunk are entry points only in overlay variant B (sub_6F00, sub_6F89, sub_6FF5, sub_7036, sub_7061, sub_708D, sub_7135, sub_7163, sub_7204, sub_7221, sub_731E, sub_7396, sub_741C, sub_74C3); in variant A they fall in the middle of routines. The main program's calls to $731E ($5639), $74C3 ($5DF2) and $7396 ($5DF5) therefore require variant B to be loaded.",
  "The map id text at $0BA9-$0BAD is the 24-bit seed $0B95-$0B97 written 5 bits per character (last character = seed bits 0-4) through the 32-character alphabet ' ABCDEFGHIJKLMNOPQRSTUVWXYZ+-*/=' at $890C (sub_7850); the same seed is what manualLookupCheck swaps in from the $0401 table to show the player a map from the manual."
 ]
}