{ "chunk": "game_main_0800_2BDD", "unit": "game/main_0800", "routines": [ { "addr": "2C7F", "name": "drawWindowFrame", "summary": "Draws one of the 27 predefined screen windows/panels selected by X: copies left column, top row, width and height from windowLeftTable/windowTopTable/windowWidthTable/windowHeightTable into fillColumn/fillRow/windowWidth/windowHeight, fetches the window's 9-tile frame set pointer from windowFramePtrTable and falls into drawFrameBox. Used by every screen builder (battlefield frames 0-2, main menu $18/$19, overlay B setup/stat screens 5-$16).", "inputs": "X = window index 0-26", "outputs": "fillColumn, fillRow, windowWidth, windowHeight set; frame drawn; windowWidth/windowHeight left decremented by drawFrameBox; A/X/Y clobbered", "confidence": "high" }, { "addr": "2CA1", "name": "drawFrameBox", "summary": "Draws a rectangular frame out of a 9-tile set (layout: 0 top-left, 1 top edge, 2 top-right, 3 left edge, 4 unused centre, 5 right edge, 6 bottom-left, 7 bottom edge, 8 bottom-right) at fillRow/fillColumn with windowWidth x windowHeight cells, corners first then the four edges via drawFrameRow/drawFrameColumn. Also called directly by overlay B ($724A) with its own tile set.", "inputs": "A/Y = lo/hi pointer to the 9-byte tile set; fillRow, fillColumn, windowWidth, windowHeight; current colour state (colourOverride/fillColourValue/charColourTable)", "outputs": "frame drawn into the bitmap and screen RAM; windowWidth and windowHeight decremented by 1 (become right/bottom extents); zp_18/zp_19 = last cell, zp_1A = right column, zp_1B = bottom row, zp_B8/zp_B9 = tile set pointer", "confidence": "high" }, { "addr": "2D1D", "name": "drawFrameColumn", "summary": "Draws tile (zp_B8),Y repeatedly down column zp_19 starting at row zp_18 until the row reaches zp_1B (exclusive). Used for the left and right frame edges.", "inputs": "Y = tile offset in the set, zp_B8/zp_B9 = tile set, zp_18 = first row, zp_19 = column, zp_1B = stop row", "outputs": "zp_18 = zp_1B; zp_1C = tile code; cells drawn", "confidence": "high" }, { "addr": "2D2F", "name": "drawFrameRow", "summary": "Draws tile (zp_B8),Y repeatedly along row zp_18 starting at column zp_19 until the column reaches zp_1A (exclusive). Used for the top and bottom frame edges.", "inputs": "Y = tile offset, zp_B8/zp_B9 = tile set, zp_18 = row, zp_19 = first column, zp_1A = stop column", "outputs": "zp_19 = zp_1A; zp_1C = tile code; cells drawn", "confidence": "high" }, { "addr": "2D41", "name": "initBitmapScreen", "summary": "Sets up the game's bitmap display from scratch: full-screen text window (sub_C0D7), zp_41 = 1, border and background shadows + registers = grey ($0C), colour RAM filled with $0C, screen RAM $8C00-$8FFF filled with $CC, bitmap $A000-$BF3F cleared, then $D016 = $C8 (hires, 40 columns), $D018 = $38 (screen $8C00, bitmap $A000 in VIC bank 2), $D011 = $3B (bitmap mode) with the shadows at $9000-$9002 updated, and finally initSpritePointers. Called from the startup code ($0318), the screen switcher ($06E8), mainStart ($0AAF) and drawBattlefieldScreen ($2636).", "inputs": "none", "outputs": "VIC registers and shadows $9000-$9004, colour RAM, screen RAM, bitmap cleared; zp_41 = 1, zp_7E = 0, zp_48/zp_49 clobbered, spriteEnableShadow = 0, sprite pointers $8FF8-$8FFF = $28-$2F", "confidence": "high" }, { "addr": "2DBD", "name": "setTerrainTilePtr", "summary": "Computes glyphPtr (zp_B2/zp_B3) = $9C6E + A*8 and clears colourOverride. A is a map byte minus $40 (the caller at $27B2 subtracts $40), so $9C6E holds the 8-byte terrain tile definitions that $27C2 expands into the 16x16-pixel battlefield cells.", "inputs": "A = terrain tile index (map byte - $40)", "outputs": "zp_B2/zp_B3 = tile pointer, colourOverride ($2A29) = 0, Y = 0", "confidence": "medium" }, { "addr": "2DDB", "name": "clearMessageLineAndScreen", "summary": "Clears the message line via $C39F and falls into clearCurrentScreenWindows. Called from the screen-switch code at $070C.", "inputs": "currentScreen ($90FB)", "outputs": "see clearCurrentScreenWindows", "confidence": "medium" }, { "addr": "2DDE", "name": "clearCurrentScreenWindows", "summary": "Re-clears the content areas of whichever screen is active (currentScreen $90FB): 0 = battlefield: sprites off, text colour 0, message window cleared (sub_261C, skipped while commPauseFlags is nonzero), overview window cleared (sub_5E03), then view window cleared (sub_26A1); 1 = overlay screen (jumps to $7A6B in overlay B); 2 = enables only sprite 1 and jumps to $821F with A = 0; 3 = jumps to the menu redraw at L_158F. Also called from the $C000 overlay ($C75E) after saving the text window, e.g. when resuming after a pause.", "inputs": "currentScreen ($90FB), commPauseFlags (zp_BE)", "outputs": "spriteEnableShadow = 0 (or 2 for screen 2), zp_3D = 0 for the battlefield; tail-calls the per-screen routine", "confidence": "medium" }, { "addr": "2E0F", "name": "magnifyWindow2x", "summary": "In-place 2x zoom of the cell rectangle at fillRow/fillColumn with windowWidth x windowHeight cells, centred on the rectangle: every source cell (8x8 bitmap block plus its colour byte) is redrawn as a 2x2 cell block at (fillRow - h/2 + 2*row, fillColumn - w/2 + 2*col) using the $C000 overlay's double-size glyph renderer sub_C24B. Processes the four quadrants from the outer edges inward so no source cell is overwritten before it is read; odd widths/heights set magnifyWidthOdd/magnifyHeightOdd and pass edge flags in zp_BA so the middle row/column is drawn half size. Waits for raster line $80 first. Used by overlay B ($7552) for the repeated zoom-in animation of a 7x6 cell area at row 9 column 16.", "inputs": "fillRow, fillColumn, windowWidth, windowHeight", "outputs": "bitmap/screen rewritten; windowWidth/windowHeight decremented when even; zp_20 = ceil(w/2), zp_21 = ceil(h/2), zp_3F = dest left column, zp_40 = dest top row, zp_30/zp_31 = row/column counters, zp_3D = colour byte of the current cell, zp_BA = edge flags, zp_B2-zp_B9 clobbered", "confidence": "medium" }, { "addr": "2F34", "name": "setMagnifyCellPointers", "summary": "Helper of magnifyWindow2x: computes the pointers of source cell (fillRow + zp_30, fillColumn + zp_31) and copies them to zp_B2/zp_B3 (bitmap, used as the glyph source) and zp_B8/zp_B9 (screen RAM, to read the colour byte), then computes the destination cell (zp_40 + 2*zp_30, zp_3F + 2*zp_31) into bitmapPtr/screenPtr via sub_2A9F.", "inputs": "zp_30 = source row offset, zp_31 = source column offset, fillRow, fillColumn, zp_3F/zp_40 = destination origin", "outputs": "zp_B2/zp_B3 = source bitmap cell, zp_B8/zp_B9 = source screen cell, zp_B4-zp_B7 = destination cell pointers, zp_18/zp_19 = destination row/column", "confidence": "high" }, { "addr": "2F6A", "name": "initSpritePointers", "summary": "Clears spriteEnableShadow and writes sprite pointers $28..$2F to $8FF8-$8FFF (the last 8 bytes of the $8C00 screen), so sprites 0-7 take their shapes from $8A00, $8A40, ... $8BC0. Called only from initBitmapScreen.", "inputs": "none", "outputs": "spriteEnableShadow = 0, $8FF8-$8FFF set, zp_48/zp_49 = $8FF8, A/X/Y clobbered", "confidence": "high" }, { "addr": "2FA5", "name": "clearAllSpriteData", "summary": "Zeroes $8A00-$8BFF, the shape data of all eight sprites. Entered by JMP from the startup code at $0386 (labelled L_2FA5 in the listing but it is a routine ending in RTS).", "inputs": "none", "outputs": "$8A00-$8BFF = 0, X = 0, A = 0", "confidence": "high" }, { "addr": "2FB2", "name": "clearSpriteShape", "summary": "Zeroes the 64-byte shape of sprite A (address $8A00 + A*64 via spriteOffsetLoTable/spriteOffsetHiTable, patched into the STA at L_2FC8). Called before any sprite shape is built (cursor shapes, formation sprite, circles, overlay screens).", "inputs": "A = sprite number 0-7", "outputs": "sprite shape cleared; A = 0, Y = $FF", "confidence": "high" }, { "addr": "2FCF", "name": "setCursorShapeSquare", "summary": "Loads the 18-byte (6 rows x 24 px) hollow-square shape at $9E6E into sprite 0 after clearing it, sets sprite 0 colour shadow ($9008) to 0 and enables sprite 0 via enableSprites. The cursor sprite is Y-expanded so the 6 rows cover a 12-pixel cell. Called when a unit is selected/being ordered ($2015, $4C53, $5E40, $5E87, $61F5, $6393).", "inputs": "none", "outputs": "sprite 0 shape and colour, spriteEnableShadow bit 0 set", "confidence": "medium" }, { "addr": "2FF3", "name": "setCursorShapeCross", "summary": "Same as setCursorShapeSquare but with the thick cross/plus shape at $9E80 (used when zp_BB is positive, see $2195).", "inputs": "none", "outputs": "sprite 0 shape = cross, colour 0, sprite 0 enabled", "confidence": "medium" }, { "addr": "2FFA", "name": "setCursorShapeBlock", "summary": "Loads the small solid-block shape at $9E92 into sprite cursorSpriteIndex ($91CC) after clearing it, and sets that sprite's colour shadow to $0C unless it is sprite 0 (sprite 0 keeps its blinking colour). Used for the plain battlefield cursor ($21A8) and for sprite 1 when no group box is active ($2550).", "inputs": "cursorSpriteIndex ($91CC) = sprite number", "outputs": "sprite shape rewritten, spriteColorShadow[n] = $0C for n != 0", "confidence": "medium" }, { "addr": "3023", "name": "mapCellToSpriteXY", "summary": "Converts a map cell (X = column, Y = row) to sprite coordinates on the overview map. When groupBoxActive ($920A) bit 7 is set it first adds the group-box left/top offsets ($922F/$9231), converts, then adds 1 to both results; otherwise it falls straight into mapCellToSpriteXYRaw.", "inputs": "X = map column, Y = map row, groupBoxActive, $922F, $9231", "outputs": "X = sprite X, Y = sprite Y", "confidence": "medium" }, { "addr": "303A", "name": "mapCellToSpriteXYRaw", "summary": "Sprite X = column*4 + $1F, sprite Y = row*4 + $39: the 40x40 battlefield is shown on the overview panel at 4x4 pixels per cell starting at screen pixel (7,7) (sprite origin offsets $18/$32).", "inputs": "X = map column 0-39, Y = map row 0-39", "outputs": "X = sprite X coordinate, Y = sprite Y coordinate", "confidence": "high" }, { "addr": "3049", "name": "positionCursorSprite", "summary": "Places sprite cursorSpriteIndex ($91CC) over the view centre cell (viewOriginCol+3, viewOriginRow+2) on the overview map: converts with mapCellToSpriteXY (group-box aware), stores into spriteXShadow/spriteYShadow and enables the sprite with the mask from spriteBitMasks. Does nothing when cursorSpriteIndex is negative.", "inputs": "zp_A5, zp_A6, cursorSpriteIndex, groupBoxActive and offsets", "outputs": "spriteXShadow[n], spriteYShadow[n], spriteEnableShadow bit n", "confidence": "high" }, { "addr": "306C", "name": "drawLineInPathSprite", "summary": "Draws a line into sprite 3 (the path/route sprite at $8AC0) using the high-memory line stepper: initLineStepper ($FBB8) from (lineCurRow,lineCurCol) to (lineEndRow,lineEndCol), then plots the current cell and steps until lineStepsLeft is 0. If A has bit 7 set, two extra steps are taken per plotted point, giving a dotted line. Called from $3B11 and overlay B $8266.", "inputs": "A = 0 solid / $80 dotted; zp_92-zp_95 line endpoints", "outputs": "pixels set in sprite 3; line stepper state consumed", "confidence": "medium" }, { "addr": "308A", "name": "plotPathCross", "summary": "Plots the four orthogonal neighbours of (lineCurRow, lineCurCol) into sprite 3: (row, col-1), (row-1, col), (row+1, col), then falls into plotPathSpritePixel for (row, col+1). Leaves lineCurCol incremented by 1. Marks the end point of a path ($3B14).", "inputs": "zp_92 = row, zp_93 = column", "outputs": "4 pixels set in sprite 3; zp_93 = column + 1", "confidence": "medium" }, { "addr": "30A1", "name": "plotPathSpritePixel", "summary": "Sets the pixel (column zp_93, row zp_92) in sprite 3's shape at $8AC0: byte offset = row*3 + column/8, bit mask from the $C000 table at $C008. Also used directly by overlay B ($7852, $785E).", "inputs": "zp_92 = row 0-20, zp_93 = column 0-23", "outputs": "one bit set in $8AC0-$8AFF; A/X/Y clobbered, operand at $30B4 patched", "confidence": "high" }, { "addr": "30C0", "name": "buildGroupFormationSprite", "summary": "Builds the group 'formation ghost' for a group move: gets the bounding box of the destination positions (unitDestCol/unitDestRow, selected by Y=$80) of all live units in group selectedGroupId via sub_4B5E, stores its min/max columns/rows in $922F/$922E and $9231/$9230, plots every member as a 2x2 pixel dot at ((destCol-minCol)*2, (destRow-minRow)*2) into spriteBuildBuffer ($9025) with spritePlotMode = set, copies the buffer to sprite 0, sets X/Y expansion for sprites 0 and 1, converts the box extents into offsets relative to the selected unit's destination (so the box follows the cursor), sets groupBoxActive = $FF, calls keepGroupBoxInsideMap and positionCursorSprite. Called from $21A2 when the group has 2 or more units.", "inputs": "selectedGroupId ($9206), selectedUnit ($90F8), unit arrays $F76C/$FB54/$F7D0/$F834", "outputs": "sprite 0 shape, spriteBuildBuffer, $922E-$9231, spriteXExpandShadow/spriteYExpandShadow bits 0-1, groupBoxActive = $FF, spritePlotMode = $40, view origin possibly nudged", "confidence": "medium" }, { "addr": "31A3", "name": "enableSprites", "summary": "ORs mask A into spriteEnableShadow ($9022) unless linkState ($922A) bit 6 is set (sprites suppressed, e.g. while the link is being set up). Widely used by cursor/marker code and the $C000 overlay.", "inputs": "A = sprite bit mask", "outputs": "spriteEnableShadow updated (or unchanged); A = combined mask", "confidence": "high" }, { "addr": "31AF", "name": "plotSpritePixel", "summary": "Plots pixel (X = column 0-23, Y = row 0-20) in the shape of sprite A according to spritePlotMode ($91F3): bit 7 set = toggle (EOR), bit 6 set = set (ORA, shares code with setSpritePixel), otherwise clear (AND with the inverted mask). A negative sprite number selects the 64-byte spriteBuildBuffer at $9025 instead of $8A00 + n*64. Self-modifies the operation opcode at $3232 and its mask operand.", "inputs": "A = sprite number (bit 7 = use $9025 buffer), X = column, Y = row, spritePlotMode", "outputs": "one pixel modified; zp_98/zp_99 = sprite row pointer, spritePlotTemp clobbered", "confidence": "high" }, { "addr": "31CA", "name": "setSpritePixel", "summary": "Entry of plotSpritePixel that forces the ORA (set) operation; the mask is still inverted when spritePlotMode has neither bit 6 nor 7 set, so callers set spritePlotMode = $40 first (buildGroupFormationSprite does).", "inputs": "A = sprite number, X = column, Y = row", "outputs": "pixel set", "confidence": "high" }, { "addr": "3239", "name": "drawCircleInSprite", "summary": "Draws a circle outline of radius X (1-10) centred at (12,10) into sprite A using 8-way octant symmetry: takes the dx and dy point lists for that radius from the tables at $9F37/$9F41 (dx pointers), $9F4B/$9F55 (dy pointers) and the step count from $9F5F, then for each step computes and plots the 8 symmetric points (spritePlotMode decides set/clear/toggle). Radius 1 gives a filled 3x3 dot. Used for range rings and blob markers ($3471, $3D13, $3D43, overlay B $7190-$71DA).", "inputs": "A = sprite number (stored to cursorSpriteIndex $91CC), X = radius index 1-10, spritePlotMode", "outputs": "circle plotted; $91CC = A, circleStepsLeft ($91F2) = -1, zp_AD-zp_B0 = table pointers", "confidence": "high" }, { "addr": "3266", "name": "computeCircleOctantPoints", "summary": "For circle step Y reads a = (zp_AD),Y and b = (zp_AF),Y and fills circlePointX[0..7] = 12+a,12+b,12-b,12-a,12-a,12-b,12+b,12+a and circlePointY[0..7] = 10+b,10+a,10+a,10+b,10-b,10-a,10-a,10-b (tables at $32DA and $32E2).", "inputs": "Y = step index, zp_AD/zp_AE and zp_AF/zp_B0 = dx/dy table pointers", "outputs": "$32DA-$32E9 filled", "confidence": "high" }, { "addr": "32BF", "name": "plotCircleOctantPoints", "summary": "Plots the 8 points prepared by computeCircleOctantPoints into sprite cursorSpriteIndex via plotSpritePixel, iterating circlePointIndex ($3238) from 7 down to 0.", "inputs": "$32DA-$32E9, cursorSpriteIndex ($91CC), spritePlotMode", "outputs": "8 pixels plotted; circlePointIndex = $FF", "confidence": "high" } ], "variables": [ { "addr": "2A29", "scope": "abs", "name": "colourOverride", "meaning": "Nonzero = colour byte forced for every character drawn by sub_2AC8 (ignores charColourTable $93B4); 0 = use the per-character default colour, and when that is 0 too, fillColourValue through the patchable colour operation", "confidence": "high" }, { "addr": "2A2A", "scope": "abs", "name": "fillColourValue", "meaning": "Current colour byte for the rectangle fill (sub_2A64) and for characters whose table colour is 0; combined with the existing colour byte through the self-modified opcode at $2B00/$2A85 (LDA/ORA/EOR/CMP selected by sub_2A47)", "confidence": "high" }, { "addr": "2A2B", "scope": "abs", "name": "useFillChar", "meaning": "Nonzero makes sub_2B18 draw fillChar in every cell of the block instead of the characters from the source string", "confidence": "high" }, { "addr": "2A2C", "scope": "abs", "name": "fillChar", "meaning": "Character code used by sub_2B18 when useFillChar is set", "confidence": "high" }, { "addr": "2A2D", "scope": "abs", "name": "fillRow", "meaning": "Top screen row (0-24) of the current window / block / fill rectangle; set from windowTopTable by drawWindowFrame", "confidence": "high" }, { "addr": "2A2E", "scope": "abs", "name": "fillColumn", "meaning": "Left screen column (0-39) of the current window / block / fill rectangle; set from windowLeftTable by drawWindowFrame", "confidence": "high" }, { "addr": "2E0D", "scope": "abs", "name": "magnifyWidthOdd", "meaning": "$FF when the window being magnified has an odd width (middle column drawn half size), else 0", "confidence": "high" }, { "addr": "2E0E", "scope": "abs", "name": "magnifyHeightOdd", "meaning": "$FF when the window being magnified has an odd height, else 0", "confidence": "high" }, { "addr": "3237", "scope": "abs", "name": "spritePlotTemp", "meaning": "Scratch of plotSpritePixel: X column, then bit index, then the row for the row*3 multiply", "confidence": "high" }, { "addr": "3238", "scope": "abs", "name": "circlePointIndex", "meaning": "Loop index 7..0 over the 8 octant points in plotCircleOctantPoints", "confidence": "high" }, { "addr": "32DA", "scope": "abs", "name": "circlePointX", "meaning": "8 sprite X coordinates of the current circle step (filled by computeCircleOctantPoints)", "confidence": "high" }, { "addr": "32E2", "scope": "abs", "name": "circlePointY", "meaning": "8 sprite Y coordinates of the current circle step", "confidence": "high" }, { "addr": "9221", "scope": "abs", "name": "windowWidth", "meaning": "Width in cells of the current window/block (from windowWidthTable or the block header); drawFrameBox and magnifyWindow2x decrement it in place", "confidence": "high" }, { "addr": "9222", "scope": "abs", "name": "windowHeight", "meaning": "Height in cells of the current window/block; decremented in place by drawFrameBox", "confidence": "high" }, { "addr": "9025", "scope": "abs", "name": "spriteBuildBuffer", "meaning": "64-byte off-screen sprite shape buffer ($9025-$9064): plotSpritePixel targets it when the sprite number is negative; buildGroupFormationSprite composes the formation there and copies it to sprite 0; overlay B uses it too ($70A6-$70CC)", "confidence": "high" }, { "addr": "91CC", "scope": "abs", "name": "cursorSpriteIndex", "meaning": "Number of the sprite currently used as cursor/marker (0 = main cursor, 1 = second cursor, 4 = blob, ...); bit 7 set = none (positionCursorSprite does nothing, plotting goes to spriteBuildBuffer). Set by drawCircleInSprite to its A argument", "confidence": "high" }, { "addr": "91F2", "scope": "abs", "name": "circleStepsLeft", "meaning": "Remaining circle steps (entries of the dx/dy lists) in drawCircleInSprite", "confidence": "high" }, { "addr": "91F3", "scope": "abs", "name": "spritePlotMode", "meaning": "Pixel operation for plotSpritePixel/drawCircleInSprite: $80 = toggle (EOR), $40 = set (ORA), 0 = clear (AND)", "confidence": "high" }, { "addr": "9206", "scope": "abs", "name": "selectedGroupId", "meaning": "Group/formation id of the selected unit group; ORed with $40 and compared against unitFlagsTable & $7C to find the members (sub_4B5E, buildGroupFormationSprite)", "confidence": "medium" }, { "addr": "9209", "scope": "abs", "name": "selectedGroupSize", "meaning": "Number of units in the current selection (1 for a single unit at $5E3D); >= 2 makes $219B build the formation sprite instead of the plain cursor", "confidence": "low" }, { "addr": "920A", "scope": "abs", "name": "groupBoxActive", "meaning": "Bit 7 set while a group formation box is attached to the cursor (set by buildGroupFormationSprite, cleared at $223E)", "confidence": "high" }, { "addr": "922E", "scope": "abs", "name": "groupBoxRightOffset", "meaning": "Column offset (>= 0) of the group box right edge from the selected unit / cursor column (max member column - selected unit column). NOTE: the 163A survey swapped this with $922F; sub_4B5E stores the maximum column in zp_1B which buildGroupFormationSprite copies here", "confidence": "medium" }, { "addr": "922F", "scope": "abs", "name": "groupBoxLeftOffset", "meaning": "Column offset (<= 0, signed) of the group box left edge from the cursor column; mapCellToSpriteXY adds it to position the formation sprite and buildGroupFormationSprite subtracts it from member columns", "confidence": "medium" }, { "addr": "9230", "scope": "abs", "name": "groupBoxBottomOffset", "meaning": "Row offset (>= 0) of the group box bottom edge from the cursor row (keepGroupBoxInsideMap scrolls up when centre + this >= limit)", "confidence": "medium" }, { "addr": "9231", "scope": "abs", "name": "groupBoxTopOffset", "meaning": "Row offset (<= 0, signed) of the group box top edge from the cursor row; added to the row by mapCellToSpriteXY", "confidence": "medium" }, { "addr": "90F8", "scope": "abs", "name": "selectedUnit", "meaning": "Index (0-99) of the selected unit; buildGroupFormationSprite uses its destination position as the box origin", "confidence": "high" }, { "addr": "90FB", "scope": "abs", "name": "currentScreen", "meaning": "Active screen id 0-3 (0 battlefield, 1 overlay-B screen, 2 overlay-B screen drawn by $821F, 3 menu) dispatched by clearCurrentScreenWindows", "confidence": "high" }, { "addr": "9000", "scope": "abs", "name": "vicCtrl1Shadow", "meaning": "Shadow of $D011 ($3B = bitmap mode) written by initBitmapScreen", "confidence": "high" }, { "addr": "9001", "scope": "abs", "name": "vicCtrl2Shadow", "meaning": "Shadow of $D016; bit 4 (multicolour) is tested by the character drawer sub_2AC8 to decide whether colour RAM must be written as well", "confidence": "high" }, { "addr": "9002", "scope": "abs", "name": "vicMemPtrShadow", "meaning": "Shadow of $D018 ($38: screen $8C00, bitmap $A000)", "confidence": "high" }, { "addr": "9003", "scope": "abs", "name": "borderColorShadow", "meaning": "Shadow of $D020", "confidence": "high" }, { "addr": "9004", "scope": "abs", "name": "bgColorShadow", "meaning": "Shadow of $D021", "confidence": "high" }, { "addr": "9008", "scope": "abs", "name": "spriteColorShadow", "meaning": "8 sprite colour shadows ($D027-$D02E); cursor shape setters write 0 (sprite 0) or $0C", "confidence": "high" }, { "addr": "9010", "scope": "abs", "name": "spriteXShadow", "meaning": "8 sprite X positions written by positionCursorSprite", "confidence": "high" }, { "addr": "9018", "scope": "abs", "name": "spriteYShadow", "meaning": "8 sprite Y positions written by positionCursorSprite", "confidence": "high" }, { "addr": "9022", "scope": "abs", "name": "spriteEnableShadow", "meaning": "Shadow of $D015; set through enableSprites, cleared by initSpritePointers/clearCurrentScreenWindows", "confidence": "high" }, { "addr": "9023", "scope": "abs", "name": "spriteXExpandShadow", "meaning": "Shadow of $D01D; bits 0-1 set for the formation sprites 0/1", "confidence": "high" }, { "addr": "9024", "scope": "abs", "name": "spriteYExpandShadow", "meaning": "Shadow of $D017; bits 0-1 set for the formation sprites 0/1", "confidence": "high" }, { "addr": "922A", "scope": "abs", "name": "linkState", "meaning": "Bit 6 set blocks enableSprites (sprites stay off)", "confidence": "medium" }, { "addr": "F76C", "scope": "abs", "name": "unitStatusTable", "meaning": "Per-unit byte (100 entries); bit 7 set = unit slot unused/dead, skipped by the group scans", "confidence": "medium" }, { "addr": "F7D0", "scope": "abs", "name": "unitDestCol", "meaning": "Per-unit destination/ordered map column (100 entries, low 7 bits; bit 7 is a flag copied into unitFlagsTable at $3DF2); selected instead of unitCol by sub_4B5E when Y bit 7 is set and used for the formation sprite", "confidence": "medium" }, { "addr": "F834", "scope": "abs", "name": "unitDestRow", "meaning": "Per-unit destination/ordered map row (100 entries, low 7 bits)", "confidence": "medium" }, { "addr": "FB54", "scope": "abs", "name": "unitFlagsTable", "meaning": "Per-unit flags; bits 2-6 hold the group id (compared with selectedGroupId | $40 after AND $7C)", "confidence": "medium" }, { "addr": "8A00", "scope": "abs", "name": "spriteShapes", "meaning": "Shape data of sprites 0-7 ($8A00-$8BFF, 64 bytes each; sprite pointers $28-$2F). Sprite 0/1 = cursors and formation ghost, sprite 3 ($8AC0) = path/route line sprite", "confidence": "high" }, { "addr": "8AC0", "scope": "abs", "name": "pathSpriteShape", "meaning": "Shape of sprite 3, the path/route line drawn by drawLineInPathSprite and plotPathSpritePixel", "confidence": "medium" }, { "addr": "9E6E", "scope": "abs", "name": "cursorShapeSquare", "meaning": "18-byte (6 rows x 3 bytes) hollow 6x6 square sprite shape, the unit-selected cursor", "confidence": "medium" }, { "addr": "9E80", "scope": "abs", "name": "cursorShapeCross", "meaning": "18-byte thick plus/cross sprite shape (used when zp_BB > 0)", "confidence": "medium" }, { "addr": "9E92", "scope": "abs", "name": "cursorShapeBlock", "meaning": "18-byte solid 4x4 block sprite shape, the plain battlefield cursor", "confidence": "medium" }, { "addr": "9C6E", "scope": "abs", "name": "terrainTileGlyphs", "meaning": "8-byte terrain tile definitions indexed by map byte - $40 (pointer computed by setTerrainTilePtr, expanded 2x at $27C2)", "confidence": "medium" }, { "addr": "9F37", "scope": "abs", "name": "circleDxPtrLo", "meaning": "10 low bytes of pointers to the dx lists of circle radii 1-10 (lists live at $9F69+)", "confidence": "high" }, { "addr": "9F41", "scope": "abs", "name": "circleDxPtrHi", "meaning": "10 high bytes (all $9F) of the circle dx list pointers", "confidence": "high" }, { "addr": "9F4B", "scope": "abs", "name": "circleDyPtrLo", "meaning": "10 low bytes of pointers to the dy lists of circle radii 1-10", "confidence": "high" }, { "addr": "9F55", "scope": "abs", "name": "circleDyPtrHi", "meaning": "10 high bytes (all $9F) of the circle dy list pointers", "confidence": "high" }, { "addr": "9F5F", "scope": "abs", "name": "circleStepCount", "meaning": "10 bytes: number of (dx,dy) entries minus 1 for each circle radius", "confidence": "high" }, { "addr": "9810", "scope": "abs", "name": "tallDigitPairs", "meaning": "10 pairs of character codes ($49/$4A ...) forming the two-row tall digits 0-9 drawn by sub_2BB7", "confidence": "medium" }, { "addr": "C008", "scope": "abs", "name": "pixelBitMasks", "meaning": "8-byte table $80,$40,...,$01 in the $C000 overlay used by the sprite plotters to select the bit for column & 7", "confidence": "high" }, { "addr": "18", "scope": "zp", "name": "cellRow", "meaning": "Screen row (0-24) of the cell addressed by sub_2A9F and the character drawer; frame and block drawers use it as their row cursor", "confidence": "high" }, { "addr": "19", "scope": "zp", "name": "cellColumn", "meaning": "Screen column (0-39) of the cell addressed by sub_2A9F; column cursor of the frame/block drawers", "confidence": "high" }, { "addr": "1A", "scope": "zp", "name": "frameRightColumn", "meaning": "In drawFrameBox: column of the right frame edge; in buildGroupFormationSprite: unit loop index", "confidence": "high" }, { "addr": "1B", "scope": "zp", "name": "frameBottomRow", "meaning": "In drawFrameBox: row of the bottom frame edge; row counter in the fill/block routines; group id compare value in buildGroupFormationSprite", "confidence": "high" }, { "addr": "1C", "scope": "zp", "name": "frameTileCode", "meaning": "Tile character repeated by drawFrameRow/drawFrameColumn (column counter in sub_2B18)", "confidence": "high" }, { "addr": "20", "scope": "zp", "name": "magnifyHalfWidth", "meaning": "ceil(windowWidth/2) during magnifyWindow2x", "confidence": "high" }, { "addr": "21", "scope": "zp", "name": "magnifyHalfHeight", "meaning": "ceil(windowHeight/2) during magnifyWindow2x", "confidence": "high" }, { "addr": "30", "scope": "zp", "name": "magnifyRow", "meaning": "Source row counter of magnifyWindow2x", "confidence": "high" }, { "addr": "31", "scope": "zp", "name": "magnifyColumn", "meaning": "Source column counter of magnifyWindow2x", "confidence": "high" }, { "addr": "3D", "scope": "zp", "name": "textColour", "meaning": "Colour byte; sub_2AC8 writes it to colour RAM in multicolour mode, magnifyWindow2x loads it with the source cell's colour, clearCurrentScreenWindows zeroes it", "confidence": "high" }, { "addr": "3F", "scope": "zp", "name": "magnifyDestColumn", "meaning": "Reused text-cursor byte: left column of the magnified output (fillColumn - half width)", "confidence": "high" }, { "addr": "40", "scope": "zp", "name": "magnifyDestRow", "meaning": "Reused text-cursor byte: top row of the magnified output (fillRow - half height)", "confidence": "high" }, { "addr": "41", "scope": "zp", "name": "flag41", "meaning": "Set to 1 by initBitmapScreen (menu-vs-battle state flag per the 0800 survey)", "confidence": "low" }, { "addr": "7E", "scope": "zp", "name": "textState7E", "meaning": "Cleared by initBitmapScreen; only otherwise touched by the $C000 text renderer ($C187 read, $C1DD write)", "confidence": "low" }, { "addr": "91", "scope": "zp", "name": "lineStepsLeft", "meaning": "Remaining line steps; drawLineInPathSprite loops until 0", "confidence": "high" }, { "addr": "92", "scope": "zp", "name": "lineCurRow", "meaning": "Current row of the line stepper = sprite row plotted by plotPathSpritePixel", "confidence": "high" }, { "addr": "93", "scope": "zp", "name": "lineCurCol", "meaning": "Current column of the line stepper = sprite column plotted by plotPathSpritePixel", "confidence": "high" }, { "addr": "98", "scope": "zp", "name": "spriteRowPtr", "meaning": "Pointer (lo) to the sprite shape byte row being plotted by plotSpritePixel", "confidence": "high" }, { "addr": "99", "scope": "zp", "name": "spriteRowPtrHi", "meaning": "Pointer (hi) for plotSpritePixel ($8A or $90)", "confidence": "high" }, { "addr": "A5", "scope": "zp", "name": "viewOriginCol", "meaning": "Map column of the view's left edge; +3 is the cursor/centre column used by positionCursorSprite", "confidence": "high" }, { "addr": "A6", "scope": "zp", "name": "viewOriginRow", "meaning": "Map row of the view's top edge; +2 is the cursor/centre row", "confidence": "high" }, { "addr": "AD", "scope": "zp", "name": "circleDxPtr", "meaning": "Pointer (lo) to the dx list of the current circle radius", "confidence": "high" }, { "addr": "AE", "scope": "zp", "name": "circleDxPtrHi", "meaning": "Pointer (hi) of circleDxPtr", "confidence": "high" }, { "addr": "AF", "scope": "zp", "name": "circleDyPtr", "meaning": "Pointer (lo) to the dy list of the current circle radius", "confidence": "high" }, { "addr": "B0", "scope": "zp", "name": "circleDyPtrHi", "meaning": "Pointer (hi) of circleDyPtr", "confidence": "high" }, { "addr": "B2", "scope": "zp", "name": "glyphPtr", "meaning": "Pointer (lo) to the 8-byte glyph source: $9426 + char*8 for characters, a bitmap cell during magnification, $9C6E + tile*8 for terrain tiles", "confidence": "high" }, { "addr": "B3", "scope": "zp", "name": "glyphPtrHi", "meaning": "Pointer (hi) of glyphPtr", "confidence": "high" }, { "addr": "B4", "scope": "zp", "name": "bitmapPtr", "meaning": "Pointer (lo) to the destination 8x8 cell in the $A000 bitmap (row table $9300/$9319 + column table $9332/$935A)", "confidence": "high" }, { "addr": "B5", "scope": "zp", "name": "bitmapPtrHi", "meaning": "Pointer (hi) of bitmapPtr", "confidence": "high" }, { "addr": "B6", "scope": "zp", "name": "screenPtr", "meaning": "Pointer (lo) to the colour byte of the cell in screen RAM $8C00 (row table $9382/$939B + column); +$4C00 reaches colour RAM", "confidence": "high" }, { "addr": "B7", "scope": "zp", "name": "screenPtrHi", "meaning": "Pointer (hi) of screenPtr", "confidence": "high" }, { "addr": "B8", "scope": "zp", "name": "blockPtr", "meaning": "Pointer (lo) to the frame tile set (drawFrameBox), the width/height/chars block (sub_2B18) or the source cell colour byte (magnify)", "confidence": "high" }, { "addr": "B9", "scope": "zp", "name": "blockPtrHi", "meaning": "Pointer (hi) of blockPtr", "confidence": "high" }, { "addr": "BA", "scope": "zp", "name": "magnifyEdgeFlags", "meaning": "Flags passed to the $C000 double-size renderer sub_C24B: bit 0 skip top half row, bit 1 skip bottom half row, bit 2 skip left half column, bit 3 skip right half column (for odd sizes)", "confidence": "medium" }, { "addr": "BB", "scope": "zp", "name": "cursorModeFlag", "meaning": "Selects the cursor shape at $218F: 0 block/formation, positive cross, negative block", "confidence": "low" }, { "addr": "BE", "scope": "zp", "name": "commPauseFlags", "meaning": "Nonzero skips clearing the message window in clearCurrentScreenWindows", "confidence": "medium" }, { "addr": "48", "scope": "zp", "name": "fillPtr", "meaning": "Pointer (lo) used by initBitmapScreen to clear the bitmap and by initSpritePointers", "confidence": "high" }, { "addr": "49", "scope": "zp", "name": "fillPtrHi", "meaning": "Pointer (hi) of fillPtr", "confidence": "high" } ], "dataBlocks": [ { "addr": "2BDD", "length": 27, "type": "byteTable", "name": "windowLeftTable", "description": "Left screen column of windows 0-26 (0 overview panel, 1 battlefield view, 2 status panel, 4, 5-$16 overlay B screens, $18/$19 main menu frames, $1A)" }, { "addr": "2BF8", "length": 27, "type": "byteTable", "name": "windowTopTable", "description": "Top screen row of windows 0-26" }, { "addr": "2C13", "length": 27, "type": "byteTable", "name": "windowWidthTable", "description": "Width in cells (including the frame) of windows 0-26; e.g. window 0 = 22x22 around the 20x20 overview map, window 1 = 16x12 around the 7x5 cell (14x10 char) battlefield view" }, { "addr": "2C2E", "length": 27, "type": "byteTable", "name": "windowHeightTable", "description": "Height in cells of windows 0-26" }, { "addr": "2C49", "length": 54, "type": "addrTable", "name": "windowFramePtrTable", "description": "27 little-endian pointers to 9-byte frame tile sets in $9824-$987D (sets at $9824, $982D, $9836, $983F, $9848 (solid), $9851 (blank), $985A, $9863, $986C, $9875)" }, { "addr": "2E0D", "length": 2, "type": "byteTable", "name": "magnifyOddFlags", "description": "magnifyWidthOdd and magnifyHeightOdd work variables" }, { "addr": "2F85", "length": 16, "type": "byteTable", "name": "spriteBitMasks", "description": "$01,$02,...,$80 (set masks for sprite n) followed by $FE,$FD,...,$7F (clear masks, used by $337D)" }, { "addr": "2F95", "length": 8, "type": "byteTable", "name": "spriteOffsetLoTable", "description": "Low byte of n*64 for sprite 0-7 ($00,$40,$80,$C0 repeating); added to $8A00" }, { "addr": "2F9D", "length": 8, "type": "byteTable", "name": "spriteOffsetHiTable", "description": "High byte of n*64 for sprite 0-7 (0,0,0,0,1,1,1,1)" }, { "addr": "3237", "length": 2, "type": "byteTable", "name": "spritePlotWork", "description": "spritePlotTemp and circlePointIndex work bytes" } ], "misclassified": [ { "addr": "2FA5", "length": 13, "actual": "code", "evidence": "Labelled L_2FA5 but it is a routine (clearAllSpriteData) entered by JMP from the startup code at $0386 and terminated by RTS; it should be a sub_/routine label." }, { "addr": "3232", "length": 2, "actual": "code", "evidence": "D_3232 carries a data label but is the self-modified ORA/AND/EOR #imm instruction of plotSpritePixel (opcode written at $31BB/$31C5/$31CF, operand at $3223). Code, not data." } ], "insights": [ "Screen cell addressing: sub_2A9F takes zp_18 = row (0-24) and zp_19 = column (0-39) and builds bitmapPtr (zp_B4/B5) from the row tables $9300 (lo)/$9319 (hi) plus the column tables $9332/$935A, and screenPtr (zp_B6/B7) from $9382/$939B plus the column. Bitmap $A000, screen RAM $8C00, colour RAM = screen + $4C00. The game font is 8 bytes per character at $9426 (sub_2AC8: glyphPtr = $9426 + char*8); $93B4 is a per-character default colour table (0 = use fillColourValue).", "Character drawing modes are self-modified opcodes: sub_2A38 (Y = 0 LDA/1 ORA/2 EOR/3 CMP=keep) patches the bitmap operation at $2AF3 and also the $C000 overlay's copy at $C22B; sub_2A47 patches the colour-byte operation at $2B00 and $2A85 (absolute forms). sub_2A56 resets both to plain copy. sub_2BCE toggles a 2x2 colour block with EOR (cursor highlight).", "The window system: 27 windows defined by four parallel byte tables at $2BDD/$2BF8/$2C13/$2C2E plus the pointer table $2C49 to 9-tile frame sets at $9824-$987D (tile order TL, T, TR, L, centre, R, BL, B, BR). Window 0 (col 0,row 0,22x22) frames the overview map, window 1 (col 24,row 0,16x12) frames the 7x5 battlefield view, window 2 (col 24,row 11,16x11) the message/status panel; windows $18/$19 are the main menu frames. sub_2B75 draws the small ring glyph $38 (a rivet) at the four inner corners of the current window; sub_2BB7 draws two-row tall digits from the pair table $9810.", "Sprites: initSpritePointers puts $28-$2F in $8FF8-$8FFF so sprite n's shape lives at $8A00 + n*64 ($8A00-$8BFF, the 'work RAM' in the overview). Sprite 0 (and 1) are the battlefield cursor / group formation ghost (Y-expanded, 18-byte shapes at $9E6E square, $9E80 cross, $9E92 block), sprite 3 ($8AC0) holds the path/route line, sprite 4 gets circle blobs. $9025-$9064 is an off-screen 64-byte sprite build buffer selected by a negative sprite number in plotSpritePixel. All sprite register writes go through the shadows $9008/$9010/$9018/$9022/$9023/$9024 copied by the IRQ.", "Overview map geometry: mapCellToSpriteXYRaw maps map cell (col,row) to sprite coordinates (col*4+$1F, row*4+$39), i.e. the whole 40x40 battlefield is drawn at 4x4 pixels per cell in the 20x20-character overview panel at screen column 1, row 1 (window 0), while the 7x5-cell zoomed view at column 25 uses 16x16 pixel cells. The cursor/centre cell is (viewOriginCol+3, viewOriginRow+2).", "Group move mechanics: buildGroupFormationSprite ($30C0) collects all live units whose unitFlagsTable & $7C equals selectedGroupId | $40, uses their destination positions (unitDestCol $F7D0 / unitDestRow $F834, low 7 bits) to plot a 2x2 dot per unit into a sprite, and stores the box extents relative to the selected unit in $922F (left, <= 0), $922E (right), $9231 (top, <= 0), $9230 (bottom). The existing 163A survey names for $922E/$922F and $9230/$9231 appear swapped (left<->right, top<->bottom); sub_4B5E and keepGroupBoxInsideMap confirm the assignment given here.", "Unit record layout seen here (100 units, parallel arrays $64 apart): $F640 unitCol, $F6A4 unitRow, $F76C status (bit 7 = dead/unused), $F7D0 destination column, $F834 destination row, $FB54 flags (bits 2-6 group id).", "Circle drawing: drawCircleInSprite ($3239) uses precomputed octant point lists for radii 1-10 at $9F69.. (pointers at $9F37/$9F41 dx, $9F4B/$9F55 dy, counts at $9F5F) and plots the 8 symmetric points about sprite pixel (12,10); spritePlotMode ($91F3: $80 toggle, $40 set, 0 clear) chooses the pixel operation. Radius 1 is a filled 3x3 dot; larger radii are outlines (range rings).", "magnifyWindow2x ($2E0F) is an in-place 2x zoom of a cell rectangle about its centre, used by overlay B ($7552) as a repeated zoom-in animation; it relies on the $C000 overlay's double-size glyph renderer sub_C24B (also used by the text printer when zp_44 bit 7 is set) and the edge flags in zp_BA.", "The disassembly file's line numbers do not match the chunk specification: lines 4395-5251 hold $29C9-$30A1; the address range $2BDD-$32D9 is at file lines 4714-5603. This survey covers the address range." ] }