{ "chunk": "game_mainProgram0800_3CF1", "unit": "game/mainProgram0800", "unitLabels": { "game/mainProgram0800": { "3D11": "ringExpandLoop", "3D41": "ringShrinkLoop", "3D6D": "comcenSystemRollLoop", "3D8E": "initUnitLoop", "3D97": "clearGroupStatusLoop", "3E2D": "recyclerSideLoop", "3E50": "recyclerScanLoop", "3E75": "respawnUnitAtRecycler", "3F77": "recyclerSpotterLoop", "3F9E": "initialFacingSideBranch", "3FF5": "haltUnitLoop", "4072": "waitFrameLoop", "409B": "runOneFrameLoop", "4120": "copyFilmViewWordLoop", "419B": "groupRedrawLoop", "42E3": "walkBackToFreeCellLoop" } }, "notes": { "3CF1": { "unit": "game/mainProgram0800", "block": [ "ringAnimStep - one byte of state for animateRingBurst below: the radius index of the", "circle frame currently being drawn into or erased from sprite 1 (1..A-1 going out,", "A-1..1 coming back)." ], "line": "radius index of the ring frame being drawn/erased (0 when the effect is idle)" }, "3CF2": { "unit": "game/mainProgram0800", "routine": [ "animateRingBurst - Expanding-then-contracting ring drawn into hardware sprite 1. It clears the sprite shape, switches the sprite on, draws circle outlines of radius 1,2,..,A-1 into it (spritePlotMode $40 = OR the pixels in), plays the explosion sound at full size and then erases the same frames in reverse order (spritePlotMode 0 = AND them out) before hiding the sprite again.", "In: A = number of radius steps, patched into the CPX operand at L_3D24+1 (drawCircleInSprite only has point lists for radii 1-10); sprite 1 must already be positioned through the shadow registers $9011/$9019.", "Out: sprite 1 shape built and cleared again, spriteEnableShadow bit 1 set and then cleared, ringAnimStep = 0, spritePlotMode left at 0, sprite1ColourShadow = $12, unusedByte7D = 0 (dead store), sound $17 played. Every wait runs through runGameFrames, so the battle keeps ticking during the animation.", "Called from: the $6F00 overlay only - $767B with A=3 (the ring that marks a unit being placed / the kick-off) and $7A0E with A=$0B (the comcen knock-out explosion)." ], "line": "patch the loop limit: A becomes the operand of the CPX at $3D24, i.e. the largest radius + 1" }, "3CF5": { "unit": "game/mainProgram0800", "line": "$12 = packed flash colour pair (high nibble 1 = white, low nibble 2 = red)" }, "3CF7": { "unit": "game/mainProgram0800", "line": "sprite 1 colour shadow; the IRQ alternates the two nibbles so the ring flashes white/red" }, "3CFA": { "unit": "game/mainProgram0800", "line": "wait until the raster is below the visible screen so building the shape does not tear (the $C000 overlay resident here is waitRasterBelowScreen; the label is the boot loader's leftover at the same address)" }, "3CFD": { "unit": "game/mainProgram0800", "line": "sprite number 1 (its shape lives at $8A00 + 1*64 = $8A40)" }, "3CFF": { "unit": "game/mainProgram0800", "line": "wipe the 64 shape bytes before the first frame is plotted" }, "3D02": { "unit": "game/mainProgram0800", "line": "bit mask %00000010 = sprite 1" }, "3D04": { "unit": "game/mainProgram0800", "line": "OR it into spriteEnableShadow ($9022) so the IRQ turns sprite 1 on" }, "3D07": { "unit": "game/mainProgram0800", "line": "plot mode $40 = set (OR) the pixels of the circle" }, "3D09": { "unit": "game/mainProgram0800", "line": "drawCircleInSprite reads spritePlotMode for every plotted point" }, "3D0C": { "unit": "game/mainProgram0800", "line": "start with radius 1 (a filled 3x3 dot)" }, "3D0E": { "unit": "game/mainProgram0800", "line": "keep the radius index in ringAnimStep as well as in X" }, "3D11": { "unit": "game/mainProgram0800", "block": [ "Growing phase: draw radius 1, 2, ... up to the caller's limit minus 1." ], "line": "sprite number 1 (drawCircleInSprite takes the sprite in A, the radius in X)" }, "3D13": { "unit": "game/mainProgram0800", "line": "plot the circle of radius X into sprite 1" }, "3D16": { "unit": "game/mainProgram0800", "line": "run 1 game frame on odd radii and none on even ones ..." }, "3D19": { "unit": "game/mainProgram0800", "line": "... so the ring grows about one step every other frame" }, "3D1B": { "unit": "game/mainProgram0800", "line": "let the engine advance while the ring expands" }, "3D1E": { "unit": "game/mainProgram0800", "line": "next radius" }, "3D21": { "unit": "game/mainProgram0800", "line": "reload it into X for the compare and for the next drawCircleInSprite" }, "3D24": { "unit": "game/mainProgram0800", "line": "operand patched at $3CF2 with the caller's step count" }, "3D26": { "unit": "game/mainProgram0800", "line": "keep growing while the radius is below the requested maximum" }, "3D28": { "unit": "game/mainProgram0800", "line": "dead store: nothing anywhere reads zp $7D" }, "3D2A": { "unit": "game/mainProgram0800", "line": "(a leftover of an earlier version of the effect)" }, "3D2C": { "unit": "game/mainProgram0800", "line": "sound $17 = the explosion noise, played at full ring size" }, "3D2E": { "unit": "game/mainProgram0800", "line": "queue it on a SID voice" }, "3D31": { "unit": "game/mainProgram0800", "line": "hold the largest ring for 5 frames" }, "3D33": { "unit": "game/mainProgram0800", "line": "the engine keeps running while we wait" }, "3D36": { "unit": "game/mainProgram0800", "line": "plot mode 0 = clear (AND out) the pixels again" }, "3D38": { "unit": "game/mainProgram0800", "line": "so the same circle calls now erase what they drew" }, "3D3B": { "unit": "game/mainProgram0800", "line": "step back to the last radius that was actually drawn" }, "3D3E": { "unit": "game/mainProgram0800", "line": "X = that radius" }, "3D41": { "unit": "game/mainProgram0800", "block": [ "Shrinking phase: erase the frames in reverse order, largest first, with no frame delay." ], "line": "sprite number 1" }, "3D43": { "unit": "game/mainProgram0800", "line": "erase the circle of radius X" }, "3D46": { "unit": "game/mainProgram0800", "line": "next smaller radius" }, "3D49": { "unit": "game/mainProgram0800", "line": "X = radius for the next pass" }, "3D4C": { "unit": "game/mainProgram0800", "line": "stop once radius 1 has been erased" }, "3D4E": { "unit": "game/mainProgram0800", "line": "clear bit 1 of the sprite enable shadow and of VIC_SPR_ENA at once" }, "3D51": { "unit": "game/mainProgram0800", "line": "sprite number 1" }, "3D53": { "unit": "game/mainProgram0800", "line": "tail call: blank its 64 shape bytes and return to the caller" }, "3D56": { "unit": "game/mainProgram0800", "block": [ "comcenSystemIndex - loop counter of rollComcenSystemDamage: the comcen system being", "rolled for, counting 3, 2, 1 (1 = BATTLE, 2 = RADAR, 3 = DRONE; index 0 is the derived", "energy status and is never damaged here)." ], "line": "the comcen system index 3..1 while the damage roll runs" }, "3D57": { "unit": "game/mainProgram0800", "routine": [ "rollComcenSystemDamage - Rolls for collateral damage to the local comcen's three repairable systems after it has been hit. It runs only in a live game with the DAMAGE rule switched on; for each system that is still healthy it sets the status to $80 (damaged) when a random 6-bit number is smaller than the damage of the hit, so a big hit is much more likely to knock a system out.", "In: hitDamage $9240 = the damage the hit just did; filmPlaybackMode $0B9B bit 7 (film / solo replay), gameTypeOptions $0BA3 bit 4 (the DAMAGE option), gameEndReason $0BA8 (0 = a game is running).", "Out: comcenSystemStatus[1..3] ($9242-$9244) may become $80 = damaged; comcenSystemIndex left 0; A/Y clobbered. The system states are never sent over the link - each machine damages and repairs its own comcen locally.", "Called from: applyHitToUnit at $3B63, only when the unit that was hit is the local comcen." ], "line": "bit 7 set = watching a game film / solo replay ..." }, "3D5A": { "unit": "game/mainProgram0800", "line": "... in which case no new damage may be rolled" }, "3D5C": { "unit": "game/mainProgram0800", "line": "the rule options of this game" }, "3D5F": { "unit": "game/mainProgram0800", "line": "bit 4 = the DAMAGE option ('comcen systems can be damaged')" }, "3D61": { "unit": "game/mainProgram0800", "line": "the systems are immune when the option is off" }, "3D63": { "unit": "game/mainProgram0800", "line": "non-zero = the battle has ended (or has not started)" }, "3D66": { "unit": "game/mainProgram0800", "line": "no damage rolls outside a running game" }, "3D68": { "unit": "game/mainProgram0800", "line": "start at system 3 (DRONE)" }, "3D6A": { "unit": "game/mainProgram0800", "line": "comcenSystemIndex is the loop counter" }, "3D6D": { "unit": "game/mainProgram0800", "block": [ "Roll once for each of the three repairable systems 3, 2, 1 (DRONE, RADAR, BATTLE)." ], "line": "Y = system index" }, "3D70": { "unit": "game/mainProgram0800", "line": "$9241+Y: 0 = healthy, $40 = degraded, $80 = damaged" }, "3D73": { "unit": "game/mainProgram0800", "line": "already damaged, nothing left to break" }, "3D75": { "unit": "game/mainProgram0800", "line": "next pseudo random byte" }, "3D78": { "unit": "game/mainProgram0800", "line": "keep 6 bits: 0..63" }, "3D7A": { "unit": "game/mainProgram0800", "line": "compare it against the damage of the hit (energy runs 0-50)" }, "3D7D": { "unit": "game/mainProgram0800", "line": "roll too high: this system survives" }, "3D7F": { "unit": "game/mainProgram0800", "line": "$80 = damaged" }, "3D81": { "unit": "game/mainProgram0800", "line": "mark the system as knocked out" }, "3D84": { "unit": "game/mainProgram0800", "line": "next lower system" }, "3D87": { "unit": "game/mainProgram0800", "line": "stop after system 1; index 0 is the energy display status, not a system" }, "3D89": { "unit": "game/mainProgram0800", "line": "return" }, "3D8A": { "unit": "game/mainProgram0800", "routine": [ "initAllUnits - Resets all 100 unit records from their start positions and puts every living unit back on the battlefield map, then clears the 16 per-group state bytes. Run once when a battle starts, after the start-position template has been copied into the unit arrays.", "In: unitColTable/unitRowTable hold the start coordinates, unitTypeTable the types and unitFlagsTable the group bits taken from the template.", "Out: every per-unit array reset by initUnitRecord/placeUnitOnMap, map cells at the unit positions set to $80|index, groupStatusTable $91F4-$9203 zeroed; scratch18 = $FF.", "Called from: overlay A (the setup phase) at $7AEF." ], "line": "start at unit 99, the last unit of side 1" }, "3D8C": { "unit": "game/mainProgram0800", "line": "initUnitRecord takes the unit index in scratch18" }, "3D8E": { "unit": "game/mainProgram0800", "block": [ "Reset units 99 down to 0." ], "line": "reset one unit and place it on the map" }, "3D91": { "unit": "game/mainProgram0800", "line": "next lower unit index" }, "3D93": { "unit": "game/mainProgram0800", "line": "loop until scratch18 goes negative (unit 0 done)" }, "3D95": { "unit": "game/mainProgram0800", "line": "16 group slots (group key = unitFlagsTable bits 2-5, i.e. 7 groups x 2 sides)" }, "3D97": { "unit": "game/mainProgram0800", "block": [ "Clear the 16 groupStatusTable entries (slowest speed class in bits 0-2, worst terrain class in bits 3-5)." ], "line": "no speed or terrain class recorded yet" }, "3D99": { "unit": "game/mainProgram0800", "line": "$91F4+Y" }, "3D9C": { "unit": "game/mainProgram0800", "line": "next group slot" }, "3D9D": { "unit": "game/mainProgram0800", "line": "until all 16 are cleared" }, "3D9F": { "unit": "game/mainProgram0800", "line": "return" }, "3DA0": { "unit": "game/mainProgram0800", "routine": [ "initUnitRecord - Resets the order state of one unit: its path position and its destination are snapped to the cell it stands on, the destination is marked 'idle', the zone-scored bits and the stun counter are cleared, the group membership is kept and the facing and side bits are rebuilt. Falls straight through into placeUnitOnMap, which finishes the reset and puts the unit on the map.", "In: scratch18 = unit index (loaded into X here).", "Out: unitPathCol/Row = unitCol/Row (cloak and blitz bits cleared), unitDestCol = col|$80 (stationary), unitDestRow = row, unitTypeTable bits 3-4 (zone scored) cleared, unitStunTable = 0, unitFlagsTable = group bits + facing + side bit; then everything placeUnitOnMap does.", "Called from: initAllUnits ($3D8E) for every unit at game start and recycleDeadUnits ($3E94) for a robot respawning at the recycler." ], "line": "X = unit index (the caller passes it in scratch18)" }, "3DA2": { "unit": "game/mainProgram0800", "line": "the unit's current map column" }, "3DA5": { "unit": "game/mainProgram0800", "line": "path position = map position; bits 6-7 (cloak) are cleared as a side effect" }, "3DA8": { "unit": "game/mainProgram0800", "line": "bit 7 of unitDestCol = 'at its destination / stationary'" }, "3DAA": { "unit": "game/mainProgram0800", "line": "destination column = where it stands, marked idle" }, "3DAD": { "unit": "game/mainProgram0800", "line": "its current map row" }, "3DB0": { "unit": "game/mainProgram0800", "line": "path row = map row; bits 6-7 (blitz) cleared with it" }, "3DB3": { "unit": "game/mainProgram0800", "line": "destination row = current row (bit 7 = 'repairing' cleared too)" }, "3DB6": { "unit": "game/mainProgram0800", "line": "type byte: bits 0-2 type, bits 3-4 zone scored, bit 6 dug in, bit 7 dead" }, "3DB9": { "unit": "game/mainProgram0800", "line": "$E7 keeps everything except bits 3 and 4 = the two 'already scored this zone' flags" }, "3DBB": { "unit": "game/mainProgram0800", "line": "so a reset unit can score its terrain zones again" }, "3DBE": { "unit": "game/mainProgram0800", "line": "clear the stun / dig-in byte ..." }, "3DC0": { "unit": "game/mainProgram0800", "line": "... no stun counter and no dig-in progress" }, "3DC3": { "unit": "game/mainProgram0800", "line": "flags: facing 0-1, group id 2-4, side 5, group member 6, blitz charge 7" }, "3DC6": { "unit": "game/mainProgram0800", "line": "$5C keeps bits 2-4 (group id) and bit 6 (is a group member)" }, "3DC8": { "unit": "game/mainProgram0800", "line": "facing, side and the one-shot charge bit are dropped" }, "3DCB": { "unit": "game/mainProgram0800", "line": "A = 0 (face up) for our units, 1 (face down) for the opponent's" }, "3DCE": { "unit": "game/mainProgram0800", "line": "merge the facing into bits 0-1" }, "3DD1": { "unit": "game/mainProgram0800", "line": "units 0-49 are side 0, 50-99 side 1 (a fixed test here, not one of the patched ones)" }, "3DD3": { "unit": "game/mainProgram0800", "line": "side 0 leaves bit 5 clear" }, "3DD5": { "unit": "game/mainProgram0800", "line": "bit 5 = side 1" }, "3DD7": { "unit": "game/mainProgram0800", "line": "store the rebuilt flag byte and fall into placeUnitOnMap" }, "3DDA": { "unit": "game/mainProgram0800", "routine": [ "placeUnitOnMap - Second half of the unit reset: restores the dug-in bit from the type byte, gives the unit a fresh movement allowance and full energy, copies the stationary and group bits into the display flags, remembers the terrain of the cell it stands on and writes $80|index into that map cell. A unit whose type byte has bit 7 set (destroyed) becomes an empty slot instead ($FF in unitTypeTable and unitTerrainUnderTable) and is not put on the map.", "In: X = scratch18 = unit index; unitColTable/unitRowTable = the cell to occupy.", "Out: unitStunTable bit 7 = dug in, unitHitFlashTable = 0, unitMovePointsTable = 10, unitEnergyTable = 50 (100%), unitDisplayFlagsTable = stationary bit + group bits, unitTerrainUnderTable = the terrain hidden by the unit, map cell at (col,row) = $80|index; mapCellPtr points at that cell, Y = 0.", "Called from: initUnitRecord (fall-through) and overlay A at $7D83, which re-places every unit once the setup phase has moved them around." ], "line": "the type byte again" }, "3DDD": { "unit": "game/mainProgram0800", "line": "bit 6 = the unit has finished digging in" }, "3DDF": { "unit": "game/mainProgram0800", "line": "shift it into bit 7 ..." }, "3DE0": { "unit": "game/mainProgram0800", "line": "... which is the 'dug in' bit of the stun / dig-in byte" }, "3DE3": { "unit": "game/mainProgram0800", "line": "clear the hit-flash / pinned countdown ..." }, "3DE5": { "unit": "game/mainProgram0800", "line": "... so the unit is drawn normally" }, "3DE8": { "unit": "game/mainProgram0800", "line": "10 movement points to start with" }, "3DEA": { "unit": "game/mainProgram0800", "line": "the signed movement-point balance ($FA28)" }, "3DED": { "unit": "game/mainProgram0800", "line": "50 energy points = 100% (the console shows it as a percentage)" }, "3DEF": { "unit": "game/mainProgram0800", "line": "energy in bits 0-5; the visibility bits 6-7 stay clear (not spotted yet)" }, "3DF2": { "unit": "game/mainProgram0800", "line": "the destination byte initUnitRecord has just written" }, "3DF5": { "unit": "game/mainProgram0800", "line": "keep only bit 7 = stationary" }, "3DF7": { "unit": "game/mainProgram0800", "line": "the display flags start as 'stationary, nothing fired, not pinned'" }, "3DFA": { "unit": "game/mainProgram0800", "line": "copy the authoritative group bits (unitFlagsTable & $7C) into the display copy" }, "3DFD": { "unit": "game/mainProgram0800", "line": "Y = unit index" }, "3DFF": { "unit": "game/mainProgram0800", "line": "X = its map column" }, "3E02": { "unit": "game/mainProgram0800", "line": "A = its map row ..." }, "3E05": { "unit": "game/mainProgram0800", "line": "... which readMapCell expects in Y" }, "3E06": { "unit": "game/mainProgram0800", "line": "A = the byte in that cell, mapCellPtr = its address, Y = 0" }, "3E09": { "unit": "game/mainProgram0800", "line": "X = unit index again" }, "3E0B": { "unit": "game/mainProgram0800", "line": "remember the terrain the unit is about to cover" }, "3E0E": { "unit": "game/mainProgram0800", "line": "the type byte" }, "3E11": { "unit": "game/mainProgram0800", "line": "bit 7 clear = the unit is alive, put it on the map" }, "3E13": { "unit": "game/mainProgram0800", "line": "$FF marks an unused unit slot ..." }, "3E15": { "unit": "game/mainProgram0800", "line": "... in the type array ..." }, "3E18": { "unit": "game/mainProgram0800", "line": "... and in the terrain-under array (= 'not on the map')" }, "3E1B": { "unit": "game/mainProgram0800", "line": "always taken ($FF is negative): a destroyed unit is not drawn" }, "3E1D": { "unit": "game/mainProgram0800", "line": "A = unit index" }, "3E1E": { "unit": "game/mainProgram0800", "line": "an occupied map cell holds $80|index" }, "3E20": { "unit": "game/mainProgram0800", "line": "claim the cell (Y is still 0 from readMapCell)" }, "3E22": { "unit": "game/mainProgram0800", "line": "return" }, "3E23": { "unit": "game/mainProgram0800", "routine": [ "recycleDeadUnits - Once per round each side that owns a recycler gets one destroyed robot back. For a side whose recycler cell is not occupied it scans that side's 50 units downwards from the comcen for destroyed ones (type bit 7 set, but not the $FF empty-slot marker). With recycler rate FULL (mode 0) the first destroyed unit found is respawned at once; with HALF (mode 1) two destroyed units of the SAME type are needed - the first is thrown away (slot set to $FF) and the second respawns. The new robot takes the recycler's coordinates, keeps its type, loses all group membership and is run through initUnitRecord.", "In: gameEndReason $0BA8 (must be 0), recyclerCol/recyclerRow $92A8/$92AA (bit 7 set = that side has no recycler, rate NONE), recyclerMode $92A6 (0 = FULL, 1 = HALF), perSideValueTable $0C8A = each side's comcen index ($31/$63), the unit arrays.", "Out: at most one unit per side respawned: its unitCol/unitRow, unitTypeTable, unitFlagsTable and everything initUnitRecord touches; for the local side pendingRedrawUnit = the new unit and message 5 'UNIT RECYCLED.' is queued. scratch18/19/1A/1B/1C/1D used as scratch.", "Called from: finishRound at $465A, i.e. once per completed round of 100 unit steps." ], "line": "0 while a battle is in progress" }, "3E26": { "unit": "game/mainProgram0800", "line": "carry on only while the game is running" }, "3E28": { "unit": "game/mainProgram0800", "line": "no recycling once the game is over" }, "3E29": { "unit": "game/mainProgram0800", "line": "start with side 1 ..." }, "3E2B": { "unit": "game/mainProgram0800", "line": "... scratch1A is the side index, counted down to 0" }, "3E2D": { "unit": "game/mainProgram0800", "block": [ "Outer loop over the two sides, 1 then 0." ], "line": "Y = side index" }, "3E2F": { "unit": "game/mainProgram0800", "line": "X = that side's recycler column; bit 7 set = no recycler (rate NONE)" }, "3E32": { "unit": "game/mainProgram0800", "line": "no recycler for this side - N is still set, so the test at $3E3B sends us on" }, "3E34": { "unit": "game/mainProgram0800", "line": "recycler row ..." }, "3E37": { "unit": "game/mainProgram0800", "line": "... readMapCell wants the row in Y and the column in X" }, "3E38": { "unit": "game/mainProgram0800", "line": "A = what is standing on the recycler cell" }, "3E3B": { "unit": "game/mainProgram0800", "line": "N set = no recycler, or a unit is parked on the recycler: skip this side" }, "3E3D": { "unit": "game/mainProgram0800", "line": "Y = side index" }, "3E3F": { "unit": "game/mainProgram0800", "line": "recycler rate: 0 = FULL (one for one), 1 = HALF (two of a type for one)" }, "3E42": { "unit": "game/mainProgram0800", "line": "patch it into the LDY immediate at $3E5B so the rate is tested inside the loop" }, "3E45": { "unit": "game/mainProgram0800", "line": "$0C8A = comcen unit index per side ($31 = 49, $63 = 99)" }, "3E48": { "unit": "game/mainProgram0800", "line": "scan downwards from the comcen" }, "3E4A": { "unit": "game/mainProgram0800", "line": "50 units per side" }, "3E4C": { "unit": "game/mainProgram0800", "line": "scratch1D = units left to examine" }, "3E4E": { "unit": "game/mainProgram0800", "line": "scratch19 = type of the previous destroyed unit; 50 can never match a real type (0-4)" }, "3E50": { "unit": "game/mainProgram0800", "block": [ "Scan this side's 50 units from the comcen downwards, looking for destroyed robots." ], "line": "X = unit being examined" }, "3E52": { "unit": "game/mainProgram0800", "line": "type byte" }, "3E55": { "unit": "game/mainProgram0800", "line": "bit 7 clear = the unit is alive, keep looking" }, "3E57": { "unit": "game/mainProgram0800", "line": "$FF = empty slot (already recycled away)" }, "3E59": { "unit": "game/mainProgram0800", "line": "not a destroyed unit either" }, "3E5B": { "unit": "game/mainProgram0800", "line": "operand patched at $3E42 with the recycler rate; the LDY sets Z from it" }, "3E5D": { "unit": "game/mainProgram0800", "line": "rate HALF: a matching pair is needed" }, "3E5F": { "unit": "game/mainProgram0800", "line": "rate FULL: keep the plain type in bits 0-2 ..." }, "3E61": { "unit": "game/mainProgram0800", "line": "... as the type of the unit to respawn" }, "3E63": { "unit": "game/mainProgram0800", "line": "always taken (a type is 0-4): respawn this one immediately" }, "3E65": { "unit": "game/mainProgram0800", "line": "rate HALF: plain type of this destroyed unit" }, "3E67": { "unit": "game/mainProgram0800", "line": "does it match the type of the destroyed unit found before it?" }, "3E69": { "unit": "game/mainProgram0800", "line": "no - remember this one and keep scanning" }, "3E6B": { "unit": "game/mainProgram0800", "line": "X = the first unit of the pair" }, "3E6D": { "unit": "game/mainProgram0800", "line": "take it off the map, restoring the terrain under it" }, "3E70": { "unit": "game/mainProgram0800", "line": "$FF = empty slot ..." }, "3E72": { "unit": "game/mainProgram0800", "line": "... the first of the pair is consumed and gone for good" }, "3E75": { "unit": "game/mainProgram0800", "block": [ "Respawn unit scratch1C of type scratch19 on the recycler cell." ], "line": "X = the unit that will come back" }, "3E77": { "unit": "game/mainProgram0800", "line": "make sure it is not on the map anywhere" }, "3E7A": { "unit": "game/mainProgram0800", "line": "Y = side index" }, "3E7C": { "unit": "game/mainProgram0800", "line": "recycler column ..." }, "3E7F": { "unit": "game/mainProgram0800", "line": "... becomes the new unit's column" }, "3E82": { "unit": "game/mainProgram0800", "line": "recycler row ..." }, "3E85": { "unit": "game/mainProgram0800", "line": "... becomes the new unit's row" }, "3E88": { "unit": "game/mainProgram0800", "line": "the plain type (bit 7 cleared, so the unit is alive again)" }, "3E8A": { "unit": "game/mainProgram0800", "line": "store it back into the type array" }, "3E8D": { "unit": "game/mainProgram0800", "line": "no facing, no group, side bit rebuilt by initUnitRecord ..." }, "3E8F": { "unit": "game/mainProgram0800", "line": "... a recycled robot always comes back ungrouped" }, "3E92": { "unit": "game/mainProgram0800", "line": "initUnitRecord takes the unit index in scratch18" }, "3E94": { "unit": "game/mainProgram0800", "line": "reset its orders and put it on the map at the recycler" }, "3E97": { "unit": "game/mainProgram0800", "line": "which side got the replacement?" }, "3E99": { "unit": "game/mainProgram0800", "line": "only tell the player about his own robots" }, "3E9C": { "unit": "game/mainProgram0800", "line": "the opponent's recycling is silent here (his machine reports it)" }, "3E9E": { "unit": "game/mainProgram0800", "line": "the new unit ..." }, "3EA0": { "unit": "game/mainProgram0800", "line": "... is queued for a redraw by the next updateGameFrame" }, "3EA3": { "unit": "game/mainProgram0800", "line": "message 5 = 'UNIT RECYCLED.'" }, "3EA5": { "unit": "game/mainProgram0800", "line": "queue it (dropped while a film is playing)" }, "3EA8": { "unit": "game/mainProgram0800", "line": "one respawn per side per round: go to the next side" }, "3EAB": { "unit": "game/mainProgram0800", "line": "remember this destroyed unit's type ..." }, "3EAD": { "unit": "game/mainProgram0800", "line": "... and its index, as the first half of a possible pair" }, "3EAF": { "unit": "game/mainProgram0800", "line": "next lower unit index" }, "3EB1": { "unit": "game/mainProgram0800", "line": "one of the 50 units done" }, "3EB3": { "unit": "game/mainProgram0800", "line": "keep scanning until this side's 50 units are exhausted" }, "3EB5": { "unit": "game/mainProgram0800", "line": "next side" }, "3EB7": { "unit": "game/mainProgram0800", "line": "both sides done" }, "3EB9": { "unit": "game/mainProgram0800", "line": "round two, for side 0" }, "3EBC": { "unit": "game/mainProgram0800", "line": "return" }, "3EBD": { "unit": "game/mainProgram0800", "routine": [ "removeUnitFromMap - Lifts unit X off the battlefield map: the terrain byte the unit was hiding is written back into its cell, the unit is marked as 'not on the map' (unitTerrainUnderTable = $FF) and its hit-flash/pinned countdown is cleared. Does nothing when the unit is not on the map already.", "In: X = unit index; unitTerrainUnderTable[X] = the saved terrain ($FF = not on the map); unitColTable/unitRowTable = its cell.", "Out: map cell restored to terrain, unitTerrainUnderTable[X] = $FF, unitHitFlashTable[X] = 0, mapCellPtr points at the cell, Y = 0.", "Called from: recycleDeadUnits ($3E6D and $3E77) and overlay A at $7D9A (clearing the board before the units are re-placed)." ], "line": "the terrain byte hidden under the unit" }, "3EC0": { "unit": "game/mainProgram0800", "line": "$FF = the unit is not on the map, nothing to restore" }, "3EC2": { "unit": "game/mainProgram0800", "routine": [ "removeUnitLeavingCell - Same as removeUnitFromMap but the caller chooses what stays behind in the unit's cell (A) instead of the saved terrain. Used when a destroyed unit should leave a wreck/marker glyph rather than plain terrain.", "In: A = byte to write into the unit's map cell, X = unit index.", "Out: map cell = A, unitTerrainUnderTable[X] = $FF, unitHitFlashTable[X] = 0, Y = 0; the LDX operand at L_3ED7+1 is patched with the unit index.", "Called from: updateHitFlashTimers at $4894, when the flash of a destroyed unit expires." ], "line": "park the unit index in the LDX operand at $3ED7 (X is needed for the column)" }, "3EC5": { "unit": "game/mainProgram0800", "line": "save the byte to leave in the cell" }, "3EC6": { "unit": "game/mainProgram0800", "line": "Y = the unit's map row" }, "3EC9": { "unit": "game/mainProgram0800", "line": "A = its map column ..." }, "3ECC": { "unit": "game/mainProgram0800", "line": "... readMapCell wants the column in X" }, "3ECD": { "unit": "game/mainProgram0800", "line": "sets mapCellPtr to the unit's cell (and returns Y = 0)" }, "3ED0": { "unit": "game/mainProgram0800", "line": "get the byte to leave behind back" }, "3ED1": { "unit": "game/mainProgram0800", "line": "index 0 of the cell pointer" }, "3ED3": { "unit": "game/mainProgram0800", "line": "write it into the map" }, "3ED5": { "unit": "game/mainProgram0800", "line": "$FF = this unit is no longer on the map" }, "3ED7": { "unit": "game/mainProgram0800", "line": "operand patched at $3EC2 with the unit index" }, "3ED9": { "unit": "game/mainProgram0800", "line": "mark the unit as off the map" }, "3EDC": { "unit": "game/mainProgram0800", "line": "clear the hit-flash / pinned countdown ..." }, "3EDE": { "unit": "game/mainProgram0800", "line": "... so nothing tries to redraw the vacated cell later" }, "3EE1": { "unit": "game/mainProgram0800", "line": "return" }, "3EE2": { "unit": "game/mainProgram0800", "routine": [ "applyBlitzStepCost - Charges a blitzing unit for the step it has just taken: it loses one energy point and gains 25 movement points (SET BLITZ = 'move extra fast'), and its next shot gets the charge bonus (unitFlagsTable bit 7). A unit that has arrived or that has run out of energy pays nothing. When the blitzing unit is the local comcen in a live battle it also starts the comcen engine sound.", "In: Y = unit index; the caller has already checked that the blitz bit (unitPathRowTable bit 7) is set; gamePhase zp_B1, filmPlaybackMode $0B9B, comcenUnit $9236, D_67CA = the sound currently on SID voice 2.", "Out: unitFlagsTable[Y] bit 7 set, one energy point taken through reduceUnitEnergyBy (hitDamage = 1), unitMovePointsTable[Y] += 25 saturating at $7F; possibly sound $16.", "Called from: processUnitStep at $433C after a successful move step, and by the blitz command handlers at $5130 / $5094." ], "line": "destination byte, bit 7 = the unit is already where it wanted to go" }, "3EE5": { "unit": "game/mainProgram0800", "line": "a stationary unit cannot blitz" }, "3EE7": { "unit": "game/mainProgram0800", "line": "energy byte" }, "3EEA": { "unit": "game/mainProgram0800", "line": "bits 0-5 = energy 0-50, bits 6-7 are visibility" }, "3EEC": { "unit": "game/mainProgram0800", "line": "no energy left, the blitz fizzles" }, "3EEE": { "unit": "game/mainProgram0800", "line": "unit flags" }, "3EF1": { "unit": "game/mainProgram0800", "line": "bit 7 = one-shot charge bonus applied to the next attack" }, "3EF3": { "unit": "game/mainProgram0800", "line": "store it back" }, "3EF6": { "unit": "game/mainProgram0800", "line": "one energy point is the price of a blitz step" }, "3EF8": { "unit": "game/mainProgram0800", "line": "hitDamage = 1, then subtract it from unit Y's energy" }, "3EFB": { "unit": "game/mainProgram0800", "line": "0 only during the real-time battle" }, "3EFD": { "unit": "game/mainProgram0800", "line": "and only when this is not a film / solo replay" }, "3F00": { "unit": "game/mainProgram0800", "line": "no engine sound outside a live battle" }, "3F02": { "unit": "game/mainProgram0800", "line": "only the local player's own comcen makes engine noise" }, "3F05": { "unit": "game/mainProgram0800", "line": "any other unit is silent" }, "3F07": { "unit": "game/mainProgram0800", "line": "the sound id currently playing on SID voice 2" }, "3F0A": { "unit": "game/mainProgram0800", "line": "voice free" }, "3F0C": { "unit": "game/mainProgram0800", "line": "$14 = comcen moving (normal speed)" }, "3F0E": { "unit": "game/mainProgram0800", "line": "the engine is already running, restart it as a blitz" }, "3F10": { "unit": "game/mainProgram0800", "line": "$16 = comcen blitzing" }, "3F12": { "unit": "game/mainProgram0800", "line": "some other effect owns the voice - do not interrupt it" }, "3F14": { "unit": "game/mainProgram0800", "line": "sound $16 = comcen engine at blitz speed" }, "3F16": { "unit": "game/mainProgram0800", "line": "request it" }, "3F19": { "unit": "game/mainProgram0800", "line": "current movement-point balance (signed)" }, "3F1C": { "unit": "game/mainProgram0800", "line": "clear the carry for the addition ..." }, "3F1D": { "unit": "game/mainProgram0800", "line": "+25 points buys roughly one extra step" }, "3F1F": { "unit": "game/mainProgram0800", "line": "no signed overflow, keep the sum" }, "3F21": { "unit": "game/mainProgram0800", "line": "clamp at +127 so the balance stays positive" }, "3F23": { "unit": "game/mainProgram0800", "line": "store the new balance" }, "3F26": { "unit": "game/mainProgram0800", "line": "return" }, "3F27": { "unit": "game/mainProgram0800", "routine": [ "drawRecyclerOnMap - Puts the recycler glyph ($67) on the battlefield map at side Y's recycler position and hands the caller everything drawMapCell needs to show it. If a unit happens to be standing on the recycler cell the glyph is stored in that unit's 'terrain under the unit' entry instead, so it appears as soon as the unit leaves, and the unit's own map byte is returned for the redraw. Returns immediately when that side has no recycler.", "In: Y = side index (0 or 1); recyclerCol $92A8 / recyclerRow $92AA (bit 7 of the column set = no recycler).", "Out: X = column, Y = row, A = the byte to draw ($67, or $80|unit when a unit is on the cell); the map cell or unitTerrainUnderTable updated; the three immediate operands at $3F51/$3F53/$3F55 are patched with the return values.", "Called from: checkEnemyRecyclerSpotted ($3F86), the recycler-placement command handler ($550D), overlay A ($7B92, $7D6A) and the modem driver ($EE47, which jumps here to draw our own recycler at game start)." ], "line": "$67 = the recycler glyph in the terrain character set" }, "3F29": { "unit": "game/mainProgram0800", "line": "default return value: patch it into the LDA at $3F54" }, "3F2C": { "unit": "game/mainProgram0800", "line": "X = recycler column; bit 7 set = this side has no recycler" }, "3F2F": { "unit": "game/mainProgram0800", "line": "there is one, go on" }, "3F31": { "unit": "game/mainProgram0800", "line": "rate NONE: nothing to draw" }, "3F32": { "unit": "game/mainProgram0800", "line": "return the column through the LDX operand at $3F50" }, "3F35": { "unit": "game/mainProgram0800", "line": "recycler row ..." }, "3F38": { "unit": "game/mainProgram0800", "line": "... into Y for readMapCell" }, "3F39": { "unit": "game/mainProgram0800", "line": "return the row through the LDY operand at $3F52" }, "3F3C": { "unit": "game/mainProgram0800", "line": "A = current contents of the recycler cell, mapCellPtr = its address" }, "3F3F": { "unit": "game/mainProgram0800", "line": "bit 7 clear = the cell is free terrain" }, "3F41": { "unit": "game/mainProgram0800", "line": "a unit is standing there: return its map byte so the caller redraws the unit" }, "3F44": { "unit": "game/mainProgram0800", "line": "Y = $80|unit index" }, "3F45": { "unit": "game/mainProgram0800", "line": "the recycler glyph ..." }, "3F47": { "unit": "game/mainProgram0800", "line": "... goes into that unit's saved-terrain slot ($FA70+$80+unit = $FAF0+unit) so it reappears when the unit moves off" }, "3F4A": { "unit": "game/mainProgram0800", "line": "always taken ($67 is not zero)" }, "3F4C": { "unit": "game/mainProgram0800", "line": "free cell: write the recycler glyph ..." }, "3F4E": { "unit": "game/mainProgram0800", "line": "... straight into the map (Y = 0 from readMapCell)" }, "3F50": { "unit": "game/mainProgram0800", "line": "operand patched at $3F32: X = recycler column" }, "3F52": { "unit": "game/mainProgram0800", "line": "operand patched at $3F39: Y = recycler row" }, "3F54": { "unit": "game/mainProgram0800", "line": "operand patched at $3F29/$3F41: A = the cell value to draw" }, "3F56": { "unit": "game/mainProgram0800", "line": "return with X/Y/A set up for drawMapCell" }, "3F57": { "unit": "game/mainProgram0800", "routine": [ "checkEnemyRecyclerSpotted - Reveals the opponent's recycler the first time one of our own uncloaked robots gets within 3 cells of it: it draws the recycler glyph on the map, queues message 8 'SPOTTED RECYCLER!' and then patches its own first byte to RTS so the test never runs again.", "In: playerSide $0B9F, perSideValueTable $0C8A (comcen index per side), the opponent's recyclerCol/recyclerRow; the area-scan lists at $0400/$0432.", "Out: the enemy recycler cell drawn, message 8 queued, the byte at $3F57 changed from NOP to RTS. The byte is set back to NOP by initIrqAndInputState ($0BBD) at game start, and the modem driver writes RTS there directly ($EE3E) when the opponent told us he has no recycler.", "Called from: finishRound at $4657, once per round." ], "line": "self-patched: NOP while the search is live, $60 (RTS) once the recycler has been spotted" }, "3F58": { "unit": "game/mainProgram0800", "line": "the local player's side (0 or 1)" }, "3F5B": { "unit": "game/mainProgram0800", "line": "the other side" }, "3F5D": { "unit": "game/mainProgram0800", "line": "X = opponent side index" }, "3F5E": { "unit": "game/mainProgram0800", "line": "patch it into the LDY operand at $3F84 for the later drawRecyclerOnMap" }, "3F61": { "unit": "game/mainProgram0800", "line": "Y = the opponent's comcen unit index ($31 or $63)" }, "3F64": { "unit": "game/mainProgram0800", "line": "set the area-scan filter to 'units of the side opposite to Y', i.e. our own units" }, "3F67": { "unit": "game/mainProgram0800", "line": "Y = the opponent's recycler row" }, "3F6A": { "unit": "game/mainProgram0800", "line": "A = its column; bit 7 set = he has no recycler" }, "3F6D": { "unit": "game/mainProgram0800", "line": "nothing to spot" }, "3F6F": { "unit": "game/mainProgram0800", "line": "X = column (collectUnitsInRadius wants column in X, row in Y)" }, "3F70": { "unit": "game/mainProgram0800", "line": "search radius 3 cells around the recycler" }, "3F72": { "unit": "game/mainProgram0800", "line": "collect our units in that square into $0400 (index) / $0432 (distance)" }, "3F75": { "unit": "game/mainProgram0800", "line": "X = $FF: no unit of ours is near the recycler" }, "3F77": { "unit": "game/mainProgram0800", "block": [ "Walk the candidate list backwards looking for one robot that is not cloaked - a cloaked robot cannot report what it sees." ], "line": "candidate unit index from the scan list at $0400+X (the label is a leftover from the copy-protection table that shares this address)" }, "3F7A": { "unit": "game/mainProgram0800", "line": "path column byte; bit 7 set = the unit is cloaked" }, "3F7D": { "unit": "game/mainProgram0800", "line": "uncloaked: the recycler has been spotted" }, "3F7F": { "unit": "game/mainProgram0800", "line": "next candidate" }, "3F80": { "unit": "game/mainProgram0800", "line": "until the list is exhausted" }, "3F82": { "unit": "game/mainProgram0800", "line": "all nearby units are cloaked - try again next round" }, "3F84": { "unit": "game/mainProgram0800", "line": "operand patched at $3F5E: Y = the opponent's side index" }, "3F86": { "unit": "game/mainProgram0800", "line": "put the recycler glyph into the map and return the cell in X/Y/A" }, "3F89": { "unit": "game/mainProgram0800", "line": "redraw that cell in the overview map and in the tactical view" }, "3F8C": { "unit": "game/mainProgram0800", "line": "message 8 = 'SPOTTED RECYCLER!'" }, "3F8E": { "unit": "game/mainProgram0800", "line": "queue it (silent while a film is playing)" }, "3F91": { "unit": "game/mainProgram0800", "line": "$60 = RTS opcode" }, "3F93": { "unit": "game/mainProgram0800", "line": "disable this routine permanently: the recycler stays visible from now on" }, "3F96": { "unit": "game/mainProgram0800", "line": "return" }, "3F97": { "unit": "game/mainProgram0800", "routine": [ "initialFacingForUnit - Returns the facing a unit starts with: 0 (up, towards the enemy) for the local player's units and 1 (down) for the opponent's, since each player sees his own side at the bottom of the map.", "In: X = unit index; the branch opcode at D_3F9E is patched by overlay A ($7BD5) to BCC when the player is side 0 and BCS when he is side 1, so 'mine' means index < 50 or index >= 50 respectively.", "Out: A = 0 or 1 (facing code, bits 0-1 of unitFlagsTable).", "Called from: initUnitRecord ($3DCB) only." ], "line": "dead instruction: the value is thrown away by the LDA #$00 that follows (leftover of an earlier version that read the side here)" }, "3F9A": { "unit": "game/mainProgram0800", "line": "assume facing 0 = up" }, "3F9C": { "unit": "game/mainProgram0800", "line": "unit index against 50, the boundary between the two sides" }, "3F9E": { "unit": "game/mainProgram0800", "line": "opcode byte patched per player side by overlay A ($7BD5): BCC for side 0, BCS for side 1" }, "3FA0": { "unit": "game/mainProgram0800", "line": "the opponent's units face the other way (facing 1 = down)" }, "3FA2": { "unit": "game/mainProgram0800", "line": "return the facing code in A" }, "3FA3": { "unit": "game/mainProgram0800", "routine": [ "mirrorMapCoordinate - Returns 39 - A, mirroring a map column or row across the 40x40 battlefield. Both machines run the same simulation but each player looks at the board from his own end, so every coordinate that crosses the link is passed through here.", "In: A = coordinate 0..39.", "Out: A = 39 - A; C reflects the internal negate, not a result.", "Called from: moveUnitToCell ($4785, zone scoring), the command handlers ($4FEA/$4FF1), overlay A ($7013), the modem driver ($ECC9) and the solo trainer (six places)." ], "line": "prepare the subtraction" }, "3FA4": { "unit": "game/mainProgram0800", "line": "A - 39" }, "3FA6": { "unit": "game/mainProgram0800", "line": "one's complement ..." }, "3FA8": { "unit": "game/mainProgram0800", "line": "clear the carry the SEC/SBC above left set ..." }, "3FA9": { "unit": "game/mainProgram0800", "line": "... plus one = two's complement, so A = 39 - A" }, "3FAB": { "unit": "game/mainProgram0800", "line": "return the mirrored coordinate" }, "3FAC": { "unit": "game/mainProgram0800", "routine": [ "haltStunnedUnit - Cancels the orders of a unit that is stunned (a drone hit stuns everything around it): its waypoint and destination are snapped back to the cell it stands on, it is marked stationary and it is dropped out of its group so the group does not wait for it. With the UNIT MENUS rule off only the comcen can be stunned this way, and in a solo game the trainer's own units (index >= 50) are left alone.", "In: Y = unit index; gameTypeOptions $0BA3 bit 3 = the UNIT MENUS option; comcenUnit $9236; soloTrainerAtGameStart $92FE bit 7.", "Out: unitPathCol/Row = unitCol/Row, unitDestCol = col|$80 (stationary), unitDestRow = row, unitFlagsTable bit 6 (group member) cleared, unitDisplayFlagsTable bit 6 cleared and bit 7 (stationary) set.", "Called from: processUnitStep at $4272 while the unit's stun counter is running, and from the drone-screen overlay at $72DF." ], "line": "the rule options of this game" }, "3FAF": { "unit": "game/mainProgram0800", "line": "bit 3 = UNIT MENUS (cloak / blitz / dig in available)" }, "3FB1": { "unit": "game/mainProgram0800", "line": "with the advanced commands on, every stunned unit is halted" }, "3FB3": { "unit": "game/mainProgram0800", "line": "with them off only the local comcen reacts to a stun ..." }, "3FB6": { "unit": "game/mainProgram0800", "line": "... every other unit keeps its orders" }, "3FB8": { "unit": "game/mainProgram0800", "line": "bit 7 = this game was started against the solo trainer" }, "3FBB": { "unit": "game/mainProgram0800", "line": "modem game: halt either side's units" }, "3FBD": { "unit": "game/mainProgram0800", "line": "units 50-99 belong to the trainer ..." }, "3FBF": { "unit": "game/mainProgram0800", "line": "... and it moves them itself, so leave them alone" }, "3FC1": { "unit": "game/mainProgram0800", "line": "the cell the unit stands on" }, "3FC4": { "unit": "game/mainProgram0800", "line": "waypoint/path column = current column (cloak bits cleared with it)" }, "3FC7": { "unit": "game/mainProgram0800", "line": "bit 7 = at destination / stationary" }, "3FC9": { "unit": "game/mainProgram0800", "line": "destination column = where it stands" }, "3FCC": { "unit": "game/mainProgram0800", "line": "current row" }, "3FCF": { "unit": "game/mainProgram0800", "line": "waypoint/path row = current row (blitz bits cleared)" }, "3FD2": { "unit": "game/mainProgram0800", "line": "destination row too - the unit has nowhere to go" }, "3FD5": { "unit": "game/mainProgram0800", "line": "unit flags" }, "3FD8": { "unit": "game/mainProgram0800", "line": "$BF clears bit 6 = 'member of a group'" }, "3FDA": { "unit": "game/mainProgram0800", "line": "a stunned unit drops out of its group" }, "3FDD": { "unit": "game/mainProgram0800", "line": "the display copy of the flags" }, "3FE0": { "unit": "game/mainProgram0800", "line": "clear its group-member bit as well ..." }, "3FE2": { "unit": "game/mainProgram0800", "line": "... and set bit 7 = stationary" }, "3FE4": { "unit": "game/mainProgram0800", "line": "store the display flags back" }, "3FE7": { "unit": "game/mainProgram0800", "line": "return" }, "3FE8": { "unit": "game/mainProgram0800", "routine": [ "haltAllUnitsAndReveal - Game-over clean-up: repairs the three comcen system status bytes, stops all 100 units (stationary, nothing fired, nothing pinned, waypoint snapped back onto the unit) and reveals the whole board by marking every unit as spotted. Redraws the overview and the tactical view when the battlefield screen is up.", "In: currentScreen $90FB (0 = battlefield).", "Out: comcenSystemStatus[1..3] = 0, unitDisplayFlagsTable bits 0-1 cleared and bit 7 set for all units, unitPathCol/Row = unitCol/Row keeping the cloak/blitz bits, every unit's visibility bits 6-7 set by markAllUnitsVisible, screen redrawn.", "Called from: endGame ($402F) only." ], "line": "0 = system healthy ..." }, "3FEA": { "unit": "game/mainProgram0800", "line": "... comcen system 1 (BATTLE)" }, "3FED": { "unit": "game/mainProgram0800", "line": "... comcen system 2 (RADAR)" }, "3FF0": { "unit": "game/mainProgram0800", "line": "... comcen system 3 (DRONE); the damage is irrelevant now" }, "3FF3": { "unit": "game/mainProgram0800", "line": "walk units 99 down to 0" }, "3FF5": { "unit": "game/mainProgram0800", "block": [ "Stop every unit and snap its waypoint back onto its real position." ], "line": "display flags of the unit" }, "3FF8": { "unit": "game/mainProgram0800", "line": "$FC clears bit 0 (fired/reloading) and bit 1 (pinned)" }, "3FFA": { "unit": "game/mainProgram0800", "line": "bit 7 = stationary" }, "3FFC": { "unit": "game/mainProgram0800", "line": "store them back" }, "3FFF": { "unit": "game/mainProgram0800", "line": "path column" }, "4002": { "unit": "game/mainProgram0800", "line": "keep bits 6-7 = the cloak flags" }, "4004": { "unit": "game/mainProgram0800", "line": "merge the unit's real column in" }, "4007": { "unit": "game/mainProgram0800", "line": "the waypoint is now the unit's own cell" }, "400A": { "unit": "game/mainProgram0800", "line": "path row" }, "400D": { "unit": "game/mainProgram0800", "line": "keep bits 6-7 = the blitz flags" }, "400F": { "unit": "game/mainProgram0800", "line": "merge the unit's real row in" }, "4012": { "unit": "game/mainProgram0800", "line": "store the waypoint row" }, "4015": { "unit": "game/mainProgram0800", "line": "next unit" }, "4016": { "unit": "game/mainProgram0800", "line": "until unit 0 has been done" }, "4018": { "unit": "game/mainProgram0800", "line": "set the visibility bits 6-7 of every unit's energy byte: the board is revealed" }, "401B": { "unit": "game/mainProgram0800", "line": "which screen is showing?" }, "401E": { "unit": "game/mainProgram0800", "line": "only the battlefield screen (0) needs redrawing" }, "4020": { "unit": "game/mainProgram0800", "line": "redraw the 40x40 overview map on the left" }, "4023": { "unit": "game/mainProgram0800", "line": "redraw the 7x5 tactical view on the right" }, "4026": { "unit": "game/mainProgram0800", "line": "return" }, "4027": { "unit": "game/mainProgram0800", "routine": [ "endGame - Ends the battle once the caller has stored the reason in gameEndReason $0BA8 ($08 = the clock ran out, $A0/$C0 = one side's comcen was knocked out). It drops any drone in flight, halts and reveals all units, works out and shows the result, and starts the countdown that takes the player back to the menu.", "In: gameEndReason $0BA8; the two 16-bit score totals $92A2-$92A5 (read by computeGameResult).", "Out: droneControlFlags $920D = 0, droneArrivalFlags $92F9 = 0, all units halted and revealed, result message queued, gameEndCountdown $92C9 = $78 (the IRQ bumps it every 8th frame; at $80 exitIfGameEnded leaves for the menu, i.e. about 64 frames later).", "Called from: applyHitToUnit at $3BD3 (comcen knocked out, via JMP) and finishRound at $460B (clock ran out)." ], "line": "clear the drone state ..." }, "4029": { "unit": "game/mainProgram0800", "line": "... no drone is under control any more" }, "402C": { "unit": "game/mainProgram0800", "line": "... and no drone is on its way in" }, "402F": { "unit": "game/mainProgram0800", "line": "stop every unit and reveal the whole board" }, "4032": { "unit": "game/mainProgram0800", "line": "compare the scores, queue the WON/LOST/DRAW message and set gameOverFlags" }, "4035": { "unit": "game/mainProgram0800", "line": "$78 = start value of the game-end countdown" }, "4037": { "unit": "game/mainProgram0800", "line": "the IRQ counts it up every 8 frames; when it reaches $80 the game returns to the menu" }, "403A": { "unit": "game/mainProgram0800", "line": "return" }, "403B": { "unit": "game/mainProgram0800", "routine": [ "markComcenRevealed - Launching a radar sweep or a drone gives the launching comcen away: it sets that side's comcen-reveal timer to 6 rounds and forces the visibility bits of that comcen so the other player can see it on the map. (The disassembler prints this routine as .byte because it is only reached through the command dispatch table.)", "In: X = 0 for our own launch, non-zero for the opponent's; playerSide $0B9F; perSideValueTable $0C8A = comcen index per side ($31/$63).", "Out: comcenRevealTimer[side] $92B2 = 6, unitEnergyTable[that comcen] bits 6-7 set (spotted + visible); for the opponent's launch the comcen is also queued for redraw in pendingRedrawUnit. A and Y clobbered.", "Called from: the radar command handler ($53BC) and the drone command handler ($53F1)." ], "line": "the local player's side" }, "403E": { "unit": "game/mainProgram0800", "line": "X = 0 means we launched" }, "4040": { "unit": "game/mainProgram0800", "line": "our own side then" }, "4042": { "unit": "game/mainProgram0800", "line": "otherwise the opponent's side" }, "4044": { "unit": "game/mainProgram0800", "line": "Y = the side whose comcen is exposed" }, "4045": { "unit": "game/mainProgram0800", "line": "6 rounds of exposure" }, "4047": { "unit": "game/mainProgram0800", "line": "$92B2+side, ticked down once per round by finishRound" }, "404A": { "unit": "game/mainProgram0800", "line": "comcen unit index of that side ($31 = 49 or $63 = 99)" }, "404D": { "unit": "game/mainProgram0800", "line": "Y = the comcen unit" }, "404E": { "unit": "game/mainProgram0800", "line": "its energy byte" }, "4051": { "unit": "game/mainProgram0800", "line": "bits 6-7 = seen this cycle + visible" }, "4053": { "unit": "game/mainProgram0800", "line": "the comcen is now drawn on the enemy's map" }, "4056": { "unit": "game/mainProgram0800", "line": "was this the opponent's launch?" }, "4058": { "unit": "game/mainProgram0800", "line": "our own comcen is already drawn, nothing to redraw" }, "405A": { "unit": "game/mainProgram0800", "line": "queue the opponent's comcen for a redraw next frame" }, "405D": { "unit": "game/mainProgram0800", "line": "return" }, "405E": { "unit": "game/mainProgram0800", "block": [ "terrainMoveCostTable - movement points charged for one step, indexed by the terrain", "class 0-7 that tryStepTowardTarget derived from the cell the unit is standing on", "(class table at $077E), or by the group's worst terrain class for a grouped unit.", "Spies always use class 0. A diagonal step costs 8 points more.", "20, 65, 30, 40, 20, 40, 20, 20 - class 1 (hills/water) is by far the most expensive.", "Read by tryStepTowardTarget ($45DA) and by rebuildGroupTables ($493A).", "Note the $077E class table can return 8, which reads one byte past the end into", "speedClassRateTable[0] = 12." ], "line": "cost per step for terrain classes 0-7: 20,65,30,40,20,40,20,20" }, "4066": { "unit": "game/mainProgram0800", "block": [ "speedClassRateTable - movement points handed out at the end of each round, indexed by", "the unit type for types 0-3 (GRUNT 12, RIDER 25, BOOMER 12, SPY 25); for a comcen", "(type 4) the index is that side's COMCEN SPEED setting ($92B4/$92B5) instead, and for", "a grouped unit it is the group's slowest class (groupStatusTable bits 0-2).", "Entry 4 = 0 means an immobile comcen.", "Read by finishRound ($46A4) and rebuildGroupTables ($4958)." ], "line": "points per round by speed class 0-4: 12,25,12,25,0" }, "406B": { "unit": "game/mainProgram0800", "block": [ "zoneScoredBitTable - the unitTypeTable bit that records 'this unit has already scored", "the terrain points of that zone': [1] = $08 for the near zone (mirrored column 10-19),", "[2] = $10 for the far zone (column 0-9). Entry 0 ($28) is never indexed - the zone", "number computed in moveUnitToCell is only ever 1 or 2.", "Read by moveUnitToCell ($479A and $47DE)." ], "line": "index 0 unused, 1 = near zone bit $08, 2 = far zone bit $10" }, "406E": { "unit": "game/mainProgram0800", "block": [ "unitMovedFlag - a single byte, not a table: set to 1 by moveUnitToCell when it really", "moved the current unit, cleared at the top of every unit step. processUnitStep reads", "it at $436F to decide whether the cell the unit came from has to be redrawn." ], "line": "1 = the current unit changed map cells during this step" }, "406F": { "unit": "game/mainProgram0800", "routine": [ "waitFramesOrInput - Interruptible pause that keeps the battle running: it advances the game one frame at a time for up to A frames and returns early on a fresh fire-button press, on the joystick leaving its centre position, or when the game-end countdown starts.", "In: A = number of frames to wait; Y = the value to hand back in Y (parked in the LDY operand at L_4091+1).", "Out: Y = the entry value, A/X clobbered, the game advanced by however many frames actually ran. The remaining frame count lives in the LDA operand at L_4085+1.", "Called from: the menu/console loops at $63C5, $6457 and $64C5." ], "line": "remember the caller's Y in the LDY operand at $4091 so it survives the wait" }, "4072": { "unit": "game/mainProgram0800", "block": [ "One frame per pass; the remaining count is kept in the LDA operand at $4085." ], "line": "store the frames still to wait into the LDA operand at $4085" }, "4075": { "unit": "game/mainProgram0800", "line": "advance the game by exactly one frame ..." }, "4077": { "unit": "game/mainProgram0800", "line": "... so the real-time battle continues during the pause" }, "407A": { "unit": "game/mainProgram0800", "line": "returns A = 0 while the fire button is held (after having been released once)" }, "407D": { "unit": "game/mainProgram0800", "line": "button down: stop waiting" }, "407F": { "unit": "game/mainProgram0800", "line": "joystick direction nibble, active low" }, "4081": { "unit": "game/mainProgram0800", "line": "$0F = centred" }, "4083": { "unit": "game/mainProgram0800", "line": "joystick pushed: stop waiting" }, "4085": { "unit": "game/mainProgram0800", "line": "operand patched at $4072: frames still to wait" }, "4087": { "unit": "game/mainProgram0800", "line": "prepare the subtraction ..." }, "4088": { "unit": "game/mainProgram0800", "line": "one frame gone" }, "408A": { "unit": "game/mainProgram0800", "line": "the whole wait has elapsed" }, "408C": { "unit": "game/mainProgram0800", "line": "non-zero once the game has ended and the menu countdown runs" }, "408F": { "unit": "game/mainProgram0800", "line": "keep waiting while the game is still live" }, "4091": { "unit": "game/mainProgram0800", "line": "operand patched at $406F: restore the caller's Y" }, "4093": { "unit": "game/mainProgram0800", "line": "return" }, "4094": { "unit": "game/mainProgram0800", "routine": [ "runGameFrames - Advances the game by A frames: each pass waits for one raster frame and then runs one updateGameFrame. Stops early once the game-end countdown has started; A = 0 returns at once. This is the routine every wait loop in the game and in both overlays uses so that the real-time battle keeps ticking while the user interface is busy.", "In: A = frame count; Y is preserved through the operand at L_40AF+1.", "Out: Y = the entry value, A/X clobbered, the game state advanced.", "Called from: 7 places in the main program ($17FA, $181F, $19B6, $3D1B, $3D33, $4077, $648A) and 9 in the $6F00 overlays." ], "line": "park the caller's Y in the LDY operand at $40AF" }, "4097": { "unit": "game/mainProgram0800", "line": "zero frames requested?" }, "4099": { "unit": "game/mainProgram0800", "line": "then there is nothing to do" }, "409B": { "unit": "game/mainProgram0800", "block": [ "One raster frame plus one engine update per pass." ], "line": "save the remaining count on the stack" }, "409C": { "unit": "game/mainProgram0800", "line": "wait for exactly one frame ..." }, "409E": { "unit": "game/mainProgram0800", "line": "... by polling the raster register" }, "40A1": { "unit": "game/mainProgram0800", "line": "run the per-frame game update" }, "40A4": { "unit": "game/mainProgram0800", "line": "get the count back" }, "40A5": { "unit": "game/mainProgram0800", "line": "prepare the subtraction ..." }, "40A6": { "unit": "game/mainProgram0800", "line": "one frame done" }, "40A8": { "unit": "game/mainProgram0800", "line": "all requested frames have run" }, "40AA": { "unit": "game/mainProgram0800", "line": "non-zero once the game has ended" }, "40AD": { "unit": "game/mainProgram0800", "line": "otherwise keep running frames" }, "40AF": { "unit": "game/mainProgram0800", "line": "operand patched at $4094: restore the caller's Y" }, "40B1": { "unit": "game/mainProgram0800", "line": "return" }, "40B2": { "unit": "game/mainProgram0800", "routine": [ "updateGameFrame - The heartbeat of the game, called once per frame from every main and user-interface loop. In order it: (1) leaves for the menu if the game-end countdown has expired; (2) handles the film replay speed keys 1-9, or takes a short cut straight to the engine while the comcen knock-out blackout is running; (3) services the status line and the CLR/HOME key, which cycles a film's viewpoint through SHOW US / SHOW THEM / SHOW BOTH; (4) handles the RUN/STOP pause and switches off the shot-line and marker sprites when their timers expire; (5) performs the pending single-unit and whole-group redraws; (6) runs the engine - every stepIntervalFrames frames one unit of the round order is stepped, every tenth unit the queued and received commands are exchanged and applied, and after all 100 units the round is finished.", "In: filmPlaybackMode $0B9B, comcenStunStatus $922A bit 6, lastTypedKey $9248, currentScreen $90FB, gamePhase zp_B1, stepDelayTimer $90FC, stepIntervalFrames $92E3, unitOrderIndex $9162, unitProcessOrder $90FD, the timers $9234/$91C8/$923E/$92C8 and the redraw requests $923A/$923B.", "Out: one unit step performed (processUnitStep) or the round finished (finishRound); map, sprites, status line and message queue updated; stepDelayTimer reloaded. May not return at all: exitIfGameEnded can jump to the menu, and handlePauseKey discards the return address while the game is paused.", "Called from: 12 places in the main program (including runGameFrames $40A1), the $6F00 overlays and the comcen blackout loop at $0737." ], "line": "jumps to the menu (and never returns) once gameEndCountdown has reached $80" }, "40B5": { "unit": "game/mainProgram0800", "line": "bit 7 set = a game film is being replayed" }, "40B8": { "unit": "game/mainProgram0800", "line": "film: the digit keys set the replay speed" }, "40BA": { "unit": "game/mainProgram0800", "line": "bit 6 = the comcen knock-out blackout at $0709 is running" }, "40BD": { "unit": "game/mainProgram0800", "line": "normal frame: do the whole update" }, "40BF": { "unit": "game/mainProgram0800", "line": "blackout: only the RUN/STOP pause handler runs ..." }, "40C2": { "unit": "game/mainProgram0800", "line": "... then go straight to the engine, leaving the screen frozen" }, "40C5": { "unit": "game/mainProgram0800", "block": [ "Film replay: keys '1'-'9' set the step interval (1 = slowest, 9 = fastest) and show 'SPEED n'." ], "line": "the last key the IRQ picked up (PETSCII with bit 7 set)" }, "40C8": { "unit": "game/mainProgram0800", "line": "prepare the subtraction ..." }, "40C9": { "unit": "game/mainProgram0800", "line": "$B1 = '1'|$80, the first speed key" }, "40CB": { "unit": "game/mainProgram0800", "line": "below '1': not a speed key" }, "40CD": { "unit": "game/mainProgram0800", "line": "nine speed keys, '1' to '9'" }, "40CF": { "unit": "game/mainProgram0800", "line": "above '9': not a speed key" }, "40D1": { "unit": "game/mainProgram0800", "line": "A = digit - 1, i.e. 0..8" }, "40D3": { "unit": "game/mainProgram0800", "line": "8 = the slowest interval ..." }, "40D5": { "unit": "game/mainProgram0800", "line": "prepare the subtraction ..." }, "40D6": { "unit": "game/mainProgram0800", "line": "interval = 8 - (digit-1) = 9 - digit ..." }, "40D8": { "unit": "game/mainProgram0800", "line": "... so '1' waits 8 frames per unit step and '9' waits none" }, "40DB": { "unit": "game/mainProgram0800", "line": "the key code again" }, "40DE": { "unit": "game/mainProgram0800", "line": "poke it into the 'SPEED *' template at $8730; its bit 7 doubles as the string terminator" }, "40E1": { "unit": "game/mainProgram0800", "line": "message 7 = 'SPEED n'" }, "40E3": { "unit": "game/mainProgram0800", "line": "queue it (always, even in a film)" }, "40E6": { "unit": "game/mainProgram0800", "line": "$FF = no key ..." }, "40E8": { "unit": "game/mainProgram0800", "line": "... so the digit is consumed and not seen again" }, "40EB": { "unit": "game/mainProgram0800", "line": "service the status line / message queue once per frame" }, "40EE": { "unit": "game/mainProgram0800", "line": "0 only during the real-time battle" }, "40F0": { "unit": "game/mainProgram0800", "line": "no viewpoint switching outside the battle" }, "40F2": { "unit": "game/mainProgram0800", "line": "screen id, 0 = the battlefield" }, "40F5": { "unit": "game/mainProgram0800", "line": "the CLR/HOME key only works on the battlefield screen" }, "40F7": { "unit": "game/mainProgram0800", "line": "the last key pressed" }, "40FA": { "unit": "game/mainProgram0800", "line": "$8C = CLR/HOME in the game's own key table" }, "40FC": { "unit": "game/mainProgram0800", "line": "some other key: nothing to do here" }, "40FE": { "unit": "game/mainProgram0800", "line": "$FF = no key ..." }, "4100": { "unit": "game/mainProgram0800", "line": "... consume CLR/HOME" }, "4103": { "unit": "game/mainProgram0800", "line": "60 frames ..." }, "4105": { "unit": "game/mainProgram0800", "line": "... of input lockout so the key does not repeat" }, "4108": { "unit": "game/mainProgram0800", "line": "bit 7 set = we are watching a film" }, "410B": { "unit": "game/mainProgram0800", "line": "in a live game CLR/HOME only eats the keypress" }, "410D": { "unit": "game/mainProgram0800", "block": [ "Cycle the film viewpoint $80 (SHOW US) - $81 (SHOW THEM) - $82 (SHOW BOTH) and back." ], "line": "clear the carry for the increment ..." }, "410E": { "unit": "game/mainProgram0800", "line": "next viewpoint" }, "4110": { "unit": "game/mainProgram0800", "line": "past SHOW BOTH?" }, "4112": { "unit": "game/mainProgram0800", "line": "no, keep it" }, "4114": { "unit": "game/mainProgram0800", "line": "wrap round to $80 = SHOW US" }, "4116": { "unit": "game/mainProgram0800", "line": "store the new playback mode (bit 7 stays set)" }, "4119": { "unit": "game/mainProgram0800", "line": "bits 0-1 = the viewpoint number 0-2" }, "411B": { "unit": "game/mainProgram0800", "line": "double ..." }, "411C": { "unit": "game/mainProgram0800", "line": "times 4: the words in filmViewWordTable are 4 bytes each" }, "411D": { "unit": "game/mainProgram0800", "line": "Y = offset of 'US ' / 'THEM' / 'BOTH'" }, "411E": { "unit": "game/mainProgram0800", "line": "X = destination index" }, "4120": { "unit": "game/mainProgram0800", "block": [ "Copy the 4-byte viewpoint word into the 'SHOW ****' message template at $8726." ], "line": "one character of the viewpoint word ($86FB in the resident overlay)" }, "4123": { "unit": "game/mainProgram0800", "line": "into the four asterisks of message 6" }, "4126": { "unit": "game/mainProgram0800", "line": "next destination byte" }, "4127": { "unit": "game/mainProgram0800", "line": "next source byte" }, "4128": { "unit": "game/mainProgram0800", "line": "all four characters copied?" }, "412A": { "unit": "game/mainProgram0800", "line": "no, keep copying" }, "412C": { "unit": "game/mainProgram0800", "line": "message 6 = 'SHOW US/THEM/BOTH'" }, "412E": { "unit": "game/mainProgram0800", "line": "queue it" }, "4131": { "unit": "game/mainProgram0800", "line": "and print it immediately" }, "4134": { "unit": "game/mainProgram0800", "line": "re-patch the 'is this unit mine' branches for the new viewpoint" }, "4137": { "unit": "game/mainProgram0800", "line": "redraw the overview map from the new point of view" }, "413A": { "unit": "game/mainProgram0800", "line": "and the tactical view" }, "413D": { "unit": "game/mainProgram0800", "line": "RUN/STOP pause handling (may drop the return address and skip the rest of the frame)" }, "4140": { "unit": "game/mainProgram0800", "line": "screen id" }, "4143": { "unit": "game/mainProgram0800", "line": "the view/shot-line timers only matter on the battlefield screen" }, "4145": { "unit": "game/mainProgram0800", "line": "countdown before the tactical view is redrawn" }, "4148": { "unit": "game/mainProgram0800", "line": "not armed" }, "414A": { "unit": "game/mainProgram0800", "line": "the IRQ stops counting at $80 ..." }, "414C": { "unit": "game/mainProgram0800", "line": "... so only the exact value $80 means 'delay expired'" }, "414E": { "unit": "game/mainProgram0800", "line": "X = left edge of the view" }, "4150": { "unit": "game/mainProgram0800", "line": "Y = top edge of the view" }, "4152": { "unit": "game/mainProgram0800", "line": "redraw the 7x5 tactical view" }, "4155": { "unit": "game/mainProgram0800", "line": "disarm the timer ..." }, "4157": { "unit": "game/mainProgram0800", "line": "... until something asks for another delayed redraw" }, "415A": { "unit": "game/mainProgram0800", "line": "countdown of the shot-line sprite" }, "415D": { "unit": "game/mainProgram0800", "line": "still visible" }, "415F": { "unit": "game/mainProgram0800", "line": "sprite enable shadow" }, "4162": { "unit": "game/mainProgram0800", "line": "$F7 clears bit 3 = sprite 3, the shot / path line" }, "4164": { "unit": "game/mainProgram0800", "line": "the line disappears when its timer runs out" }, "4167": { "unit": "game/mainProgram0800", "line": "screen id" }, "416A": { "unit": "game/mainProgram0800", "line": "only even screen ids (battlefield and the missile screen) use the marker sprite" }, "416C": { "unit": "game/mainProgram0800", "line": "odd screen: leave the sprites alone" }, "416E": { "unit": "game/mainProgram0800", "line": "countdown of the marker sprite" }, "4171": { "unit": "game/mainProgram0800", "line": "still running" }, "4173": { "unit": "game/mainProgram0800", "line": "sprite enable shadow" }, "4176": { "unit": "game/mainProgram0800", "line": "$EF clears bit 4 = sprite 4, the repair/target marker" }, "4178": { "unit": "game/mainProgram0800", "line": "switch the marker off when its timer expires" }, "417B": { "unit": "game/mainProgram0800", "block": [ "Deferred redraws: one single unit and one whole group can be queued from anywhere." ], "line": "unit queued for a redraw ($FF = none)" }, "417E": { "unit": "game/mainProgram0800", "line": "nothing queued" }, "4180": { "unit": "game/mainProgram0800", "line": "terrain under that unit; $FF = it is not on the map" }, "4183": { "unit": "game/mainProgram0800", "line": "a unit that is off the map cannot be drawn" }, "4185": { "unit": "game/mainProgram0800", "line": "draw the unit in the overview and in the tactical view" }, "4188": { "unit": "game/mainProgram0800", "line": "$FF = nothing queued ..." }, "418A": { "unit": "game/mainProgram0800", "line": "... the request has been serviced" }, "418D": { "unit": "game/mainProgram0800", "line": "group key queued for a redraw ($FF = none)" }, "4190": { "unit": "game/mainProgram0800", "line": "nothing queued: go on to the engine" }, "4192": { "unit": "game/mainProgram0800", "line": "bit 6 = 'is a group member', so the value matches unitFlagsTable & $7C" }, "4194": { "unit": "game/mainProgram0800", "line": "store the completed group key back" }, "4197": { "unit": "game/mainProgram0800", "line": "walk units 99 ..." }, "4199": { "unit": "game/mainProgram0800", "line": "... down to 0" }, "419B": { "unit": "game/mainProgram0800", "block": [ "Redraw every living unit that belongs to the queued group." ], "line": "Y = unit index" }, "419D": { "unit": "game/mainProgram0800", "line": "type byte" }, "41A0": { "unit": "game/mainProgram0800", "line": "bit 7 set = dead or unused slot" }, "41A2": { "unit": "game/mainProgram0800", "line": "unit flags" }, "41A5": { "unit": "game/mainProgram0800", "line": "$7C = group id bits 2-4, side bit 5, member bit 6" }, "41A7": { "unit": "game/mainProgram0800", "line": "does this unit belong to the queued group?" }, "41AA": { "unit": "game/mainProgram0800", "line": "no" }, "41AC": { "unit": "game/mainProgram0800", "line": "terrain under the unit" }, "41AF": { "unit": "game/mainProgram0800", "line": "$FF = the unit is not on the map" }, "41B1": { "unit": "game/mainProgram0800", "line": "redraw it" }, "41B4": { "unit": "game/mainProgram0800", "line": "next unit" }, "41B6": { "unit": "game/mainProgram0800", "line": "until unit 0 has been checked" }, "41B8": { "unit": "game/mainProgram0800", "line": "$FF = nothing queued ..." }, "41BA": { "unit": "game/mainProgram0800", "line": "... the group redraw is done" }, "41BD": { "unit": "game/mainProgram0800", "block": [ "The engine proper: one unit of the round order is stepped every stepIntervalFrames frames." ], "line": "frames still to wait before the next unit step (counted down by the IRQ)" }, "41C0": { "unit": "game/mainProgram0800", "line": "not time yet - nothing more to do this frame" }, "41C2": { "unit": "game/mainProgram0800", "line": "screen-shake counter, set when the comcen is hit" }, "41C5": { "unit": "game/mainProgram0800", "line": "no shake running" }, "41C7": { "unit": "game/mainProgram0800", "line": "jitter the fine scroll registers by a random amount" }, "41CA": { "unit": "game/mainProgram0800", "line": "one shake frame used up" }, "41CD": { "unit": "game/mainProgram0800", "line": "frames between unit steps (film speed keys change this)" }, "41D0": { "unit": "game/mainProgram0800", "line": "10 units between command exchanges during the battle" }, "41D2": { "unit": "game/mainProgram0800", "line": "0 = the battle is running" }, "41D4": { "unit": "game/mainProgram0800", "line": "battle: keep both values" }, "41D6": { "unit": "game/mainProgram0800", "line": "setup phase: exchange commands only every 50 units ..." }, "41D8": { "unit": "game/mainProgram0800", "line": "... and run the unit steps as fast as possible" }, "41DA": { "unit": "game/mainProgram0800", "line": "reload the inter-step delay" }, "41DD": { "unit": "game/mainProgram0800", "line": "position in the round order, 0-99" }, "41E0": { "unit": "game/mainProgram0800", "line": "index 0 needs no exchange (finishRound has just run one)" }, "41E2": { "unit": "game/mainProgram0800", "line": "high byte of the dividend = 0" }, "41E4": { "unit": "game/mainProgram0800", "line": "A = index / X, Y = index mod X (X is 10, or 50 in the setup phase)" }, "41E7": { "unit": "game/mainProgram0800", "line": "remainder zero?" }, "41E9": { "unit": "game/mainProgram0800", "line": "not on a time-slice boundary - step the unit without an exchange" }, "41EB": { "unit": "game/mainProgram0800", "line": "the time slice number (0-9) that identifies this exchange in the film" }, "41EE": { "unit": "game/mainProgram0800", "line": "swap packets with the opponent and execute both sides' commands" }, "41F1": { "unit": "game/mainProgram0800", "line": "non-zero = the link watchdog is still waiting for the opponent's packet" }, "41F4": { "unit": "game/mainProgram0800", "line": "the opponent is late: postpone the step and try again next frame" }, "41F6": { "unit": "game/mainProgram0800", "line": "bit 7 = the commands of this slice have been applied ..." }, "41F8": { "unit": "game/mainProgram0800", "line": "... which the setup and console screens poll" }, "41FB": { "unit": "game/mainProgram0800", "line": "age the per-unit hit-flash timers and redraw the cells whose flash ended" }, "41FE": { "unit": "game/mainProgram0800", "line": "position in the round order" }, "4201": { "unit": "game/mainProgram0800", "line": "X = the unit to process, taken from the shuffled round order" }, "4204": { "unit": "game/mainProgram0800", "line": "100 units make one round" }, "4206": { "unit": "game/mainProgram0800", "line": "still units left: step this one (falls into processUnitStep, not a JSR)" }, "4208": { "unit": "game/mainProgram0800", "line": "all 100 done: finish the round" }, "420A": { "unit": "game/mainProgram0800", "line": "opponent not ready: clear the delay ..." }, "420C": { "unit": "game/mainProgram0800", "line": "... so the exchange is retried on the very next frame" }, "420F": { "unit": "game/mainProgram0800", "line": "return" }, "4210": { "unit": "game/mainProgram0800", "line": "tick the game clock, hand out movement points and rebuild the round order" }, "4213": { "unit": "game/mainProgram0800", "routine": [ "processUnitStep - Processes exactly one unit of the round order; this is an internal entry of updateGameFrame reached by the branch at $4206, not by a JSR, and it ends by falling into the shared tail at L_4537 which bumps unitOrderIndex. Broadly it: shuffles two entries of the base order with the lock-step RNG so both machines agree; skips dead units and everything once the game is over; counts a stunned unit's stun down and halts it; turns a moving unit toward its destination; realises a unit whose path position has run ahead of its map position by walking back to the first free cell; takes one movement step with tryStepTowardTarget, paying terrain and blitz costs; redraws the cell it vacated; keeps the tactical view on a directed unit; and then (past this chunk, from $43A4) spots enemies, picks a target, fires, digs in or repairs.", "In: X = unit index taken from unitProcessOrder[unitOrderIndex]; the unit record arrays $F640-$FBB7; gamePhase zp_B1; gameEndReason $0BA8; gameOverFlags $923F.", "Out: the unit's position, path position, movement points, flags and energy updated; map cells and the screen redrawn; currentUnit $90FA, scanUnitType $9228, scanUnitTerrain $9227, currentUnitCloakBits $922C, currentUnitCol/Row $91DA/$91DB set for the helper routines; exchangeActivityFlag $92C2 set when the unit really moved.", "Called from: updateGameFrame at $4206 (BCC), once per unit step." ], "line": "currentUnit is the unit index every helper in this routine reads" }, "4216": { "unit": "game/mainProgram0800", "line": "$C0 once the game is over (set by computeGameResult)" }, "4219": { "unit": "game/mainProgram0800", "line": "game still running: process the unit" }, "421B": { "unit": "game/mainProgram0800", "line": "everything frozen: just advance the round order" }, "421E": { "unit": "game/mainProgram0800", "line": "0 = the real-time battle" }, "4220": { "unit": "game/mainProgram0800", "line": "battle: shuffle the round order" }, "4222": { "unit": "game/mainProgram0800", "line": "setup phase: 80 movement points ..." }, "4224": { "unit": "game/mainProgram0800", "line": "... so units reach their placement instantly" }, "4227": { "unit": "game/mainProgram0800", "line": "always taken ($50 is not zero): skip the shuffle in the setup phase" }, "4229": { "unit": "game/mainProgram0800", "line": "swap two random entries of the base order, using the lock-step RNG so both machines shuffle identically" }, "422C": { "unit": "game/mainProgram0800", "line": "X = the unit being processed" }, "422F": { "unit": "game/mainProgram0800", "line": "type byte" }, "4232": { "unit": "game/mainProgram0800", "line": "bit 7 set = destroyed or an empty slot: nothing to do" }, "4234": { "unit": "game/mainProgram0800", "line": "bits 0-2 = the type 0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN" }, "4236": { "unit": "game/mainProgram0800", "line": "the sight/attack scans and the scoring code read it from here" }, "4239": { "unit": "game/mainProgram0800", "line": "clear the 'this unit changed cells' flag ..." }, "423B": { "unit": "game/mainProgram0800", "line": "... moveUnitToCell sets it again if the unit really moves" }, "423E": { "unit": "game/mainProgram0800", "line": "path column byte" }, "4241": { "unit": "game/mainProgram0800", "line": "bits 6-7 = the cloak flags" }, "4243": { "unit": "game/mainProgram0800", "line": "kept aside so they survive the path-column rewrite at $4350" }, "4246": { "unit": "game/mainProgram0800", "line": "the terrain byte hidden under the unit" }, "4249": { "unit": "game/mainProgram0800", "line": "used for the sight bonus, the move cost and to redraw the vacated cell" }, "424C": { "unit": "game/mainProgram0800", "line": "non-zero once the battle has ended" }, "424F": { "unit": "game/mainProgram0800", "line": "game over: skip movement and combat, go to the tail" }, "4251": { "unit": "game/mainProgram0800", "line": "stun / dig-in byte" }, "4254": { "unit": "game/mainProgram0800", "line": "low nibble = rounds of stun left" }, "4256": { "unit": "game/mainProgram0800", "line": "not stunned: normal processing" }, "4258": { "unit": "game/mainProgram0800", "block": [ "Stunned unit: count one round of stun off, keep the dig-in nibble, and cancel its orders." ], "line": "keep the stun count in Y" }, "4259": { "unit": "game/mainProgram0800", "line": "read the byte again ..." }, "425C": { "unit": "game/mainProgram0800", "line": "... and isolate the dig-in progress in bits 4-7" }, "425E": { "unit": "game/mainProgram0800", "line": "patch it into the ORA operand at $4265 so it survives the decrement" }, "4261": { "unit": "game/mainProgram0800", "line": "A = stun count" }, "4262": { "unit": "game/mainProgram0800", "line": "prepare the subtraction ..." }, "4263": { "unit": "game/mainProgram0800", "line": "one round of stun served" }, "4265": { "unit": "game/mainProgram0800", "line": "operand patched at $425E: put the dig-in nibble back" }, "4267": { "unit": "game/mainProgram0800", "line": "store the new stun/dig-in byte" }, "426A": { "unit": "game/mainProgram0800", "line": "a stunned unit gets no movement points ..." }, "426C": { "unit": "game/mainProgram0800", "line": "... so it cannot move even if it had orders" }, "426F": { "unit": "game/mainProgram0800", "line": "Y = unit index" }, "4272": { "unit": "game/mainProgram0800", "line": "cancel its orders and drop it out of its group" }, "4275": { "unit": "game/mainProgram0800", "line": "skip movement and combat for this unit" }, "4278": { "unit": "game/mainProgram0800", "block": [ "Moving unit: point it at its destination so the facing bits and the scans agree." ], "line": "recompute bit 7 of unitDestCol: set when the destination equals the unit's cell" }, "427B": { "unit": "game/mainProgram0800", "line": "already at its destination: no facing update needed" }, "427D": { "unit": "game/mainProgram0800", "line": "X = unit index" }, "4280": { "unit": "game/mainProgram0800", "line": "destination column ..." }, "4283": { "unit": "game/mainProgram0800", "line": "... without the stationary bit" }, "4285": { "unit": "game/mainProgram0800", "line": "target column of the line walker" }, "4287": { "unit": "game/mainProgram0800", "line": "destination row ..." }, "428A": { "unit": "game/mainProgram0800", "line": "... without the repairing bit" }, "428C": { "unit": "game/mainProgram0800", "line": "target row of the line walker" }, "428E": { "unit": "game/mainProgram0800", "line": "set the line walker up from the unit's cell; A = principal direction 0-3" }, "4291": { "unit": "game/mainProgram0800", "line": "keep the direction" }, "4293": { "unit": "game/mainProgram0800", "line": "X = unit index" }, "4296": { "unit": "game/mainProgram0800", "line": "unit flags" }, "4299": { "unit": "game/mainProgram0800", "line": "$FC clears the facing bits 0-1" }, "429B": { "unit": "game/mainProgram0800", "line": "insert the new facing" }, "429D": { "unit": "game/mainProgram0800", "line": "the unit now faces the way it is walking" }, "42A0": { "unit": "game/mainProgram0800", "line": "start or stop the local comcen's engine sound to match its movement" }, "42A3": { "unit": "game/mainProgram0800", "line": "X = unit index" }, "42A6": { "unit": "game/mainProgram0800", "line": "the column the unit is standing on ..." }, "42A9": { "unit": "game/mainProgram0800", "line": "... as the line walker's target ..." }, "42AB": { "unit": "game/mainProgram0800", "line": "... and remembered so the vacated cell can be redrawn at $4377" }, "42AE": { "unit": "game/mainProgram0800", "line": "the row it is standing on ..." }, "42B1": { "unit": "game/mainProgram0800", "line": "... as the line walker's target row ..." }, "42B3": { "unit": "game/mainProgram0800", "line": "... and remembered for the redraw" }, "42B6": { "unit": "game/mainProgram0800", "line": "destination row ..." }, "42B9": { "unit": "game/mainProgram0800", "line": "... without the repairing bit" }, "42BB": { "unit": "game/mainProgram0800", "line": "is the unit already on its destination row?" }, "42BD": { "unit": "game/mainProgram0800", "line": "no: it still has somewhere to go" }, "42BF": { "unit": "game/mainProgram0800", "line": "destination column ..." }, "42C2": { "unit": "game/mainProgram0800", "line": "... without the stationary bit" }, "42C4": { "unit": "game/mainProgram0800", "line": "and on its destination column?" }, "42C6": { "unit": "game/mainProgram0800", "line": "no: it still has somewhere to go" }, "42C8": { "unit": "game/mainProgram0800", "line": "the unit has arrived: no movement this step" }, "42CB": { "unit": "game/mainProgram0800", "block": [ "A unit may step onto an occupied cell: then only its path position (F898/F8FC) moves", "while its map position stays behind. Here the map position is dragged forward again,", "one cell at a time, to the first free cell on the way to the path position." ], "line": "path row ..." }, "42CE": { "unit": "game/mainProgram0800", "line": "... without the blitz bits ..." }, "42D0": { "unit": "game/mainProgram0800", "line": "... as the line walker's current row" }, "42D2": { "unit": "game/mainProgram0800", "line": "path column ..." }, "42D5": { "unit": "game/mainProgram0800", "line": "... without the cloak bits ..." }, "42D7": { "unit": "game/mainProgram0800", "line": "... as the line walker's current column" }, "42D9": { "unit": "game/mainProgram0800", "line": "path column against the unit's real column" }, "42DB": { "unit": "game/mainProgram0800", "line": "different: the unit is out of step with its path" }, "42DD": { "unit": "game/mainProgram0800", "line": "compare the rows too" }, "42DF": { "unit": "game/mainProgram0800", "line": "compare the path row with the unit's real row" }, "42E1": { "unit": "game/mainProgram0800", "line": "path position equals map position: nothing to catch up" }, "42E3": { "unit": "game/mainProgram0800", "block": [ "Walk from the path position back toward the map position until a free cell turns up." ], "line": "X = column of the cell being tested" }, "42E5": { "unit": "game/mainProgram0800", "line": "Y = its row" }, "42E7": { "unit": "game/mainProgram0800", "line": "A = the cell contents, mapCellPtr = its address" }, "42EA": { "unit": "game/mainProgram0800", "line": "bit 7 clear: this cell is free, the unit can materialise here" }, "42EC": { "unit": "game/mainProgram0800", "line": "re-aim the walker from this cell at the unit's real cell" }, "42EF": { "unit": "game/mainProgram0800", "line": "step one cell closer" }, "42F2": { "unit": "game/mainProgram0800", "line": "still cells left to try" }, "42F4": { "unit": "game/mainProgram0800", "line": "arrived at the unit's own cell without finding a gap" }, "42F6": { "unit": "game/mainProgram0800", "line": "keep the terrain byte of the free cell for moveUnitToCell" }, "42F8": { "unit": "game/mainProgram0800", "line": "move the unit's map position into that cell" }, "42FB": { "unit": "game/mainProgram0800", "block": [ "Now take one real step from the path position toward the destination." ], "line": "Y = unit index" }, "42FE": { "unit": "game/mainProgram0800", "line": "destination column (the stationary bit is clear here, we tested it above) ..." }, "4301": { "unit": "game/mainProgram0800", "line": "... is the walker's target column" }, "4303": { "unit": "game/mainProgram0800", "line": "destination row ..." }, "4306": { "unit": "game/mainProgram0800", "line": "... is the walker's target row" }, "4308": { "unit": "game/mainProgram0800", "line": "path column ..." }, "430B": { "unit": "game/mainProgram0800", "line": "... without the cloak bits ..." }, "430D": { "unit": "game/mainProgram0800", "line": "... is where the step starts" }, "430F": { "unit": "game/mainProgram0800", "line": "path row ..." }, "4312": { "unit": "game/mainProgram0800", "line": "... without the blitz bits ..." }, "4314": { "unit": "game/mainProgram0800", "line": "... is where the step starts" }, "4316": { "unit": "game/mainProgram0800", "line": "try one step: C=1 and A = points left if it is allowed, C=0 if not" }, "4319": { "unit": "game/mainProgram0800", "line": "step accepted" }, "431B": { "unit": "game/mainProgram0800", "line": "X = $FF when the cell was blocked or off the map, = the unit index when the points ran out" }, "431C": { "unit": "game/mainProgram0800", "line": "not enough movement points: just wait for the next round" }, "431E": { "unit": "game/mainProgram0800", "line": "blocked: X = unit index again" }, "4321": { "unit": "game/mainProgram0800", "line": "park the balance at 15 points ..." }, "4323": { "unit": "game/mainProgram0800", "line": "... so the unit retries soon instead of banking points" }, "4326": { "unit": "game/mainProgram0800", "line": "no movement this step" }, "4329": { "unit": "game/mainProgram0800", "line": "Y = unit index" }, "432C": { "unit": "game/mainProgram0800", "line": "store the balance left after paying for the step" }, "432F": { "unit": "game/mainProgram0800", "line": "0 = the real-time battle" }, "4331": { "unit": "game/mainProgram0800", "line": "no blitz costs during the setup phase" }, "4333": { "unit": "game/mainProgram0800", "line": "path row bit 7 = blitz active" }, "4336": { "unit": "game/mainProgram0800", "line": "not blitzing" }, "4338": { "unit": "game/mainProgram0800", "line": "cell byte returned by tryStepTowardTarget; 0 = no step was actually taken" }, "433A": { "unit": "game/mainProgram0800", "line": "nothing moved, nothing to pay for" }, "433C": { "unit": "game/mainProgram0800", "line": "pay one energy point for +25 movement points and arm the charge bonus" }, "433F": { "unit": "game/mainProgram0800", "line": "the cell the unit stepped into" }, "4341": { "unit": "game/mainProgram0800", "line": "bit 7 set = another unit is standing there: only the path position advances" }, "4343": { "unit": "game/mainProgram0800", "line": "0 = no step taken at all" }, "4345": { "unit": "game/mainProgram0800", "line": "free cell: really move the unit and redraw both cells" }, "4348": { "unit": "game/mainProgram0800", "line": "X = unit index" }, "434B": { "unit": "game/mainProgram0800", "line": "new path column ..." }, "434D": { "unit": "game/mainProgram0800", "line": "... with the cloak bits put back ..." }, "4350": { "unit": "game/mainProgram0800", "line": "... stored as the unit's new path position" }, "4353": { "unit": "game/mainProgram0800", "line": "old path row" }, "4356": { "unit": "game/mainProgram0800", "line": "keep bits 6-7 = the blitz flags" }, "4358": { "unit": "game/mainProgram0800", "line": "merge the new row in" }, "435A": { "unit": "game/mainProgram0800", "line": "store the new path row" }, "435D": { "unit": "game/mainProgram0800", "line": "1 = something happened this turn ..." }, "435F": { "unit": "game/mainProgram0800", "line": "... which the setup phase uses to decide whether the clock ticks" }, "4362": { "unit": "game/mainProgram0800", "line": "steps the line walker still has to go" }, "4364": { "unit": "game/mainProgram0800", "line": "the unit is still on its way" }, "4366": { "unit": "game/mainProgram0800", "line": "it has reached its destination cell - is that cell occupied?" }, "4368": { "unit": "game/mainProgram0800", "line": "no, the unit is really there" }, "436A": { "unit": "game/mainProgram0800", "line": "100 movement points ..." }, "436C": { "unit": "game/mainProgram0800", "line": "... so a unit blocked on its own destination keeps pushing every round" }, "436F": { "unit": "game/mainProgram0800", "block": [ "Common tail: repaint the cell the unit left, keep the view on it, refresh its flags." ], "line": "did moveUnitToCell actually move the unit?" }, "4372": { "unit": "game/mainProgram0800", "line": "no: the old cell still shows the unit" }, "4374": { "unit": "game/mainProgram0800", "line": "Y = the row it started the step on" }, "4377": { "unit": "game/mainProgram0800", "line": "X = the column it started on" }, "437A": { "unit": "game/mainProgram0800", "line": "A = the terrain that was hidden under it" }, "437D": { "unit": "game/mainProgram0800", "line": "repaint that cell in the overview map and in the tactical view" }, "4380": { "unit": "game/mainProgram0800", "line": "if this unit is the one being directed, scroll the view onto it" }, "4383": { "unit": "game/mainProgram0800", "line": "recompute the stationary bit; returns A = unitDestCol byte, Y = currentUnit" }, "4386": { "unit": "game/mainProgram0800", "line": "keep bit 7 = stationary" }, "4388": { "unit": "game/mainProgram0800", "line": "patch it into the ORA operand at $4390" }, "438B": { "unit": "game/mainProgram0800", "line": "the display copy of the flags" }, "438E": { "unit": "game/mainProgram0800", "line": "clear its bit 7" }, "4390": { "unit": "game/mainProgram0800", "line": "operand patched at $4388: the fresh stationary bit" }, "4392": { "unit": "game/mainProgram0800", "line": "so the console shows MOVE or READY correctly" }, "4395": { "unit": "game/mainProgram0800", "line": "unit flags" }, "4398": { "unit": "game/mainProgram0800", "line": "bits 0-1 = the direction the unit faces" }, "439A": { "unit": "game/mainProgram0800", "line": "the sight and attack scans prefer targets in this direction" }, "439C": { "unit": "game/mainProgram0800", "line": "path column bit 7 = the unit is cloaked" }, "439F": { "unit": "game/mainProgram0800", "line": "not cloaked: go on to spot and fire (next chunk, from $43A4)" }, "43A1": { "unit": "game/mainProgram0800", "line": "a cloaked unit neither spots nor fires: jump to the tail" } }, "dataTypes": { "3CF1": [ "byte", 1 ], "3D56": [ "byte", 1 ], "405E": [ "byte", 8 ], "4066": [ "byte", 5 ], "406B": [ "byte", 3 ], "406E": [ "byte", 1 ] }, "zpComments": { "19": "general scratch, partner of scratch18: product/quotient, character column in the text plotters, second half of 16-bit sums; in the movement engine it carries the map byte of the cell a unit is stepping into (0 = no step taken, bit 7 set = the cell is occupied)", "1A": "general scratch: third math temp, right column of a frame box, low byte of the trainer's 16-bit scores; recycleDeadUnits uses it as the side index, counting 1 then 0", "1B": "general scratch: bottom row of a frame box, division remainder (lo), max column returned by computeGroupBounds; recycleDeadUnits keeps the first destroyed unit of a HALF-rate pair in it", "1C": "general scratch: frame tile code, division remainder (mid), member count / max row of a group; recycleDeadUnits uses it as the unit index it scans downwards from the comcen", "1D": "general scratch: bounds mode flag of computeGroupBounds (bit 7 = use destinations), division remainder (hi); recycleDeadUnits uses it as the count of units still to examine (50 per side)", "6B": "direction 0-3 of the unit's chosen target, copied into the facing bits of unitFlagsTable; processUnitStep pre-loads it at $439A with the unit's current facing so the sight and attack scans prefer that quadrant; the map generator reuses it as the blob's tried-direction mask" } }