modemwars/annotations/20_game_mainProgram0800_3671.json
2026-08-23 02:09:40 -05:00

3333 lines
91 KiB
JSON

{
"chunk": "game_mainProgram0800_3671",
"unit": "game/mainProgram0800",
"unitLabels": {
"game/mainProgram0800": {
"369C": "beginLineOfSightWalk",
"36A5": "losWalkNextCell",
"3735": "rejectScanCandidate",
"373C": "nextScanCandidate",
"3745": "scanCandidatesDone",
"379C": "leftoverScanSideBranch",
"37FE": "scanSideFilterCompare",
"3800": "scanSideFilterBranch",
"3859": "scanReverseLimit",
"38BC": "combatAttackerUnit",
"38BD": "combatDamage",
"38BE": "combatAttackerRow",
"38BF": "combatAttackerCol",
"38C0": "combatTargetRow",
"38C1": "combatTargetCol",
"393E": "loadChargeEnergySpent",
"3970": "subtractDugInDiscount",
"3A4D": "loadShotSoundId",
"3B52": "applyHitToUnitNoAlert",
"3BF6": "orVisibilityBits",
"3C0E": "subtractPinnedDamage",
"3C6F": "countBoomersLimit",
"3C8D": "loadBoomerUnit"
}
},
"notes": {
"3671": {
"unit": "game/mainProgram0800",
"block": [
"Spotting pass only (scan mode zp_20 bit 7 clear): the two kinds of target that need more than",
"plain range. A = candidate unit type 0-4, X = its index in the candidate lists, Y = its unit",
"index, zp_18 = the same list index."
],
"line": "is the candidate a SPY (type 3)?"
},
"3673": {
"unit": "game/mainProgram0800",
"line": "no - fall through to the cloak rule"
},
"3675": {
"unit": "game/mainProgram0800",
"line": "type of the unit that is doing the looking"
},
"3678": {
"unit": "game/mainProgram0800",
"line": "only a SPY can see another SPY"
},
"367A": {
"unit": "game/mainProgram0800",
"line": "anything else never notices it -> drop the candidate"
},
"367C": {
"unit": "game/mainProgram0800",
"line": "squared distance from the $0432 list"
},
"367F": {
"unit": "game/mainProgram0800",
"line": "a spy is visible only inside dist^2 20"
},
"3681": {
"unit": "game/mainProgram0800",
"line": "farther away -> drop the candidate"
},
"3683": {
"unit": "game/mainProgram0800",
"line": "close enough, but it still needs a clear line of sight (always taken, C=0 here)"
},
"3685": {
"unit": "game/mainProgram0800",
"block": [
"A cloaked unit ($F898 bit 7, set by the CLOAKING ON command) can only be made out from close",
"up, and a SPY has twice the range for it."
],
"line": "cloak flag of the candidate ($F898 bit 7)"
},
"3688": {
"unit": "game/mainProgram0800",
"line": "not cloaked -> ordinary line-of-sight test"
},
"368A": {
"unit": "game/mainProgram0800",
"line": "squared distance to the cloaked unit"
},
"368D": {
"unit": "game/mainProgram0800",
"line": "nobody sees a cloaked unit beyond dist^2 12"
},
"368F": {
"unit": "game/mainProgram0800",
"line": "too far -> drop the candidate"
},
"3691": {
"unit": "game/mainProgram0800",
"line": "type of the spotter (this overwrites the candidate index in Y; $36AC reloads it)"
},
"3694": {
"unit": "game/mainProgram0800",
"line": "is the spotter itself a SPY?"
},
"3696": {
"unit": "game/mainProgram0800",
"line": "a spy detects cloaked units out to the full dist^2 12"
},
"3698": {
"unit": "game/mainProgram0800",
"line": "everyone else only inside dist^2 6 (A still holds the distance)"
},
"369A": {
"unit": "game/mainProgram0800",
"line": "too far for a non-spy -> drop the candidate"
},
"369C": {
"unit": "game/mainProgram0800",
"block": [
"Line-of-sight walk. The line from the spotter to the candidate was set up by",
"initLineBetweenUnits at $3633; zp_1E carries the terrain height of the previous cell and zp_1F",
"the ridge state, both used by checkLineOfSightStep."
],
"line": "$80 = the ground has not risen yet along this line"
},
"369E": {
"unit": "game/mainProgram0800",
"line": "reset the ridge tracker"
},
"36A0": {
"unit": "game/mainProgram0800",
"line": "terrain height under the spotter itself"
},
"36A3": {
"unit": "game/mainProgram0800",
"line": "the height walk starts at the spotter's own level"
},
"36A5": {
"unit": "game/mainProgram0800",
"line": "advance the line by one map cell"
},
"36A8": {
"unit": "game/mainProgram0800",
"line": "steps left -> this is a cell between the two units"
},
"36AA": {
"unit": "game/mainProgram0800",
"block": [
"The line has reached the candidate's own cell: forest cover and one last height step."
],
"line": "index of the candidate in the scan lists"
},
"36AC": {
"unit": "game/mainProgram0800",
"line": "its unit index (the $0400 candidate list lies over the boot checksum table)"
},
"36AF": {
"unit": "game/mainProgram0800",
"line": "map byte hidden underneath the candidate ($FAF0)"
},
"36B2": {
"unit": "game/mainProgram0800",
"line": "-> terrain class 0-8 (the table is addressed with the raw cell byte)"
},
"36B5": {
"unit": "game/mainProgram0800",
"line": "class 2 = forest"
},
"36B7": {
"unit": "game/mainProgram0800",
"line": "open ground -> only the height test is left"
},
"36B9": {
"unit": "game/mainProgram0800",
"line": "energy byte; bit 7 = the unit is already visible"
},
"36BC": {
"unit": "game/mainProgram0800",
"line": "a unit that has been seen once stays visible even in the trees"
},
"36BE": {
"unit": "game/mainProgram0800",
"line": "back to the candidate's list index"
},
"36C0": {
"unit": "game/mainProgram0800",
"line": "its squared distance"
},
"36C3": {
"unit": "game/mainProgram0800",
"line": "inside dist^2 6 the trees do not hide it"
},
"36C5": {
"unit": "game/mainProgram0800",
"line": "farther away and in forest -> not visible"
},
"36C7": {
"unit": "game/mainProgram0800",
"line": "terrain byte under the candidate again (X was reused above)"
},
"36CA": {
"unit": "game/mainProgram0800",
"line": "-> terrain class"
},
"36CD": {
"unit": "game/mainProgram0800",
"line": "checkLineOfSightStep wants the class in X"
},
"36CE": {
"unit": "game/mainProgram0800",
"line": "last height step: does a ridge stand between us and the target?"
},
"36D1": {
"unit": "game/mainProgram0800",
"line": "blocked -> not visible"
},
"36D3": {
"unit": "game/mainProgram0800",
"line": "unit 50 = first unit of side 1: is the candidate one of the local player's units?"
},
"36D5": {
"unit": "game/mainProgram0800",
"line": "branch opcode patched per side by overlay A ($7BD8: $90 BCC for side 0, $B0 BCS for side 1) - our own unit, accept it without a message"
},
"36D7": {
"unit": "game/mainProgram0800",
"line": "an enemy: bit 7 = it was already visible"
},
"36DA": {
"unit": "game/mainProgram0800",
"line": "not a first sighting -> no message"
},
"36DC": {
"unit": "game/mainProgram0800",
"line": "candidate's type byte"
},
"36DF": {
"unit": "game/mainProgram0800",
"line": "strip the dead/dug-in bits"
},
"36E1": {
"unit": "game/mainProgram0800",
"line": "only SPY (3) and COMCEN (4) are announced"
},
"36E3": {
"unit": "game/mainProgram0800",
"line": "grunt/rider/boomer -> accept silently"
},
"36E5": {
"unit": "game/mainProgram0800",
"line": "not a spy -> test for the comcen"
},
"36E7": {
"unit": "game/mainProgram0800",
"line": "message $0F 'SPOTTED ENEMY SPY!'"
},
"36E9": {
"unit": "game/mainProgram0800",
"line": "(always taken)"
},
"36EB": {
"unit": "game/mainProgram0800",
"line": "type 4 = COMCEN?"
},
"36ED": {
"unit": "game/mainProgram0800",
"line": "neither -> accept silently"
},
"36EF": {
"unit": "game/mainProgram0800",
"line": "message $0D 'SPOTTED ENEMY COMCEN!'"
},
"36F1": {
"unit": "game/mainProgram0800",
"line": "queue it (dropped during solo/film playback)"
},
"36F4": {
"unit": "game/mainProgram0800",
"line": "candidate accepted, on to the next one"
},
"36F7": {
"unit": "game/mainProgram0800",
"block": [
"An intermediate cell of the line. A unit standing in it can stop a shot but never blocks",
"plain spotting; forest and a ridge stop both."
],
"line": "column the line walker has reached"
},
"36F9": {
"unit": "game/mainProgram0800",
"line": "and its row"
},
"36FB": {
"unit": "game/mainProgram0800",
"line": "read that map cell (N set = a unit stands there)"
},
"36FE": {
"unit": "game/mainProgram0800",
"line": "bare terrain -> straight to the height test"
},
"3700": {
"unit": "game/mainProgram0800",
"line": "unit cell: Y = $80|index, which the base-$80 alias tables undo"
},
"3701": {
"unit": "game/mainProgram0800",
"line": "scan mode"
},
"3703": {
"unit": "game/mainProgram0800",
"line": "spotting pass: units in the way never block the view"
},
"3705": {
"unit": "game/mainProgram0800",
"line": "attack pass: type of the unit standing in the line of fire ($F76C-$80)"
},
"3708": {
"unit": "game/mainProgram0800",
"line": "strip the dead/dug-in bits"
},
"370A": {
"unit": "game/mainProgram0800",
"line": "type 3 = SPY"
},
"370C": {
"unit": "game/mainProgram0800",
"line": "a spy is too small to stop a shot"
},
"370E": {
"unit": "game/mainProgram0800",
"line": "flags of the blocking unit ($F708-$80)"
},
"3711": {
"unit": "game/mainProgram0800",
"line": "the unit that wants to fire"
},
"3713": {
"unit": "game/mainProgram0800",
"line": "compare the two flag bytes"
},
"3716": {
"unit": "game/mainProgram0800",
"line": "bit 5 = side"
},
"3718": {
"unit": "game/mainProgram0800",
"line": "an enemy unit in the way stops the shot"
},
"371A": {
"unit": "game/mainProgram0800",
"line": "one of ours in the way: are we firing from high ground?"
},
"371D": {
"unit": "game/mainProgram0800",
"line": "yes -> shoot over his head"
},
"371F": {
"unit": "game/mainProgram0800",
"line": "on the flat a friendly unit blocks the shot (always taken)"
},
"3721": {
"unit": "game/mainProgram0800",
"line": "terrain byte hidden under that unit ($FAF0-$80)"
},
"3724": {
"unit": "game/mainProgram0800",
"line": "map byte -> table index"
},
"3725": {
"unit": "game/mainProgram0800",
"line": "-> terrain class of this cell"
},
"3728": {
"unit": "game/mainProgram0800",
"line": "checkLineOfSightStep wants the class in X"
},
"3729": {
"unit": "game/mainProgram0800",
"line": "class 2 = forest"
},
"372B": {
"unit": "game/mainProgram0800",
"line": "woods in the way cut the line completely"
},
"372D": {
"unit": "game/mainProgram0800",
"line": "feed this cell's height into the ridge test"
},
"3730": {
"unit": "game/mainProgram0800",
"line": "a ridge cuts the line -> candidate not visible"
},
"3732": {
"unit": "game/mainProgram0800",
"line": "keep walking toward the target"
},
"3735": {
"unit": "game/mainProgram0800",
"block": [
"Candidate rejected: $FF in its slot of the $0400 list tells every reader to ignore the entry.",
"$373C is the tail of the per-candidate loop whose head is at $3623."
],
"line": "index of the candidate"
},
"3737": {
"unit": "game/mainProgram0800",
"line": "$FF = 'no unit'"
},
"3739": {
"unit": "game/mainProgram0800",
"line": "strike it off the candidate list"
},
"373C": {
"unit": "game/mainProgram0800",
"line": "the list is walked backwards"
},
"373E": {
"unit": "game/mainProgram0800",
"line": "reload the index"
},
"3740": {
"unit": "game/mainProgram0800",
"line": "index went negative -> every candidate has been tested"
},
"3742": {
"unit": "game/mainProgram0800",
"line": "test the next candidate"
},
"3745": {
"unit": "game/mainProgram0800",
"line": "back to the caller (the per-unit update loop or selectBoomerTarget)"
},
"3746": {
"unit": "game/mainProgram0800",
"routine": [
"checkLineOfSightStep - Feeds the terrain class of one more cell into the running line-of-sight test. It compares the height of that cell with the height of the previous one: a rise is remembered in zp_1F, and a fall after a rise means a ridge stands between the two units and hides the target. Equal heights, or a fall while the line has never risen (simply looking downhill), leave the line clear.",
"In: X = terrain class 0-7 of the next cell, zp_1E = height of the previous cell, zp_1F = $80 until the line has gone uphill",
"Out: C = 1 blocked / C = 0 still clear, zp_1E = height of this cell, zp_1F = size of the rise; A and X clobbered",
"Called from: scanVisibleEnemies ($36CE for the target's own cell, $372D for each cell in between)"
],
"line": "height 0-4 of this terrain class"
},
"3749": {
"unit": "game/mainProgram0800",
"line": "keep it as the new 'previous height'"
},
"374A": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"374B": {
"unit": "game/mainProgram0800",
"line": "difference to the height of the previous cell"
},
"374D": {
"unit": "game/mainProgram0800",
"line": "same level -> nothing changes, the line stays clear"
},
"374F": {
"unit": "game/mainProgram0800",
"line": "remember this cell's height for the next step"
},
"3751": {
"unit": "game/mainProgram0800",
"line": "borrow: the ground drops here"
},
"3753": {
"unit": "game/mainProgram0800",
"line": "the ground rises: record the rise, which clears the $80 'no rise yet' marker"
},
"3755": {
"unit": "game/mainProgram0800",
"line": "C = 0: the line of sight is still clear"
},
"3756": {
"unit": "game/mainProgram0800",
"line": "back to the walk"
},
"3757": {
"unit": "game/mainProgram0800",
"line": "the ground is dropping - had it risen before?"
},
"3759": {
"unit": "game/mainProgram0800",
"line": "still $80: we are only looking downhill, and that hides nothing"
},
"375B": {
"unit": "game/mainProgram0800",
"line": "a drop after a rise = a ridge in between"
},
"375C": {
"unit": "game/mainProgram0800",
"line": "C = 1: the target is hidden"
},
"375D": {
"unit": "game/mainProgram0800",
"routine": [
"initLineBetweenUnits - Points the line walker at unit Y's cell and falls into initLineFromUnit, which sets the start to unit X's cell. Together they are the standard 'walk from unit X to unit Y' set-up.",
"In: X = unit the line starts at, Y = unit the line ends at",
"Out: zp_94/zp_95 = target row/column plus everything initLineFromUnit produces (A = direction code 0-3, line walker state zp_8C-zp_97)",
"Called from: scanVisibleEnemies ($3633)"
],
"line": "column of the far unit"
},
"3760": {
"unit": "game/mainProgram0800",
"line": "line walker target column"
},
"3762": {
"unit": "game/mainProgram0800",
"line": "row of the far unit"
},
"3765": {
"unit": "game/mainProgram0800",
"line": "line walker target row (falls into initLineFromUnit)"
},
"3767": {
"unit": "game/mainProgram0800",
"routine": [
"initLineFromUnit - Sets the line walker start to unit X's cell, runs initLineStepper ($FBB8) and reports the principal direction of the line: 0 = north or 1 = south for a row-major line, 2 = east or 3 = west for a column-major one. That value is the unit facing code kept in bits 0-1 of $F708 (stored at $4464 and used by the combat facing bonus).",
"In: X = unit the line starts at, zp_94/zp_95 = target row/column",
"Out: A = direction code 0-3, line walker state zp_8C-zp_97 set up (X = 1 for a row-major line)",
"Called from: initLineBetweenUnits (falls through), $428E where a unit is turned toward its destination"
],
"line": "column of the unit the line starts at"
},
"376A": {
"unit": "game/mainProgram0800",
"line": "line walker start column"
},
"376C": {
"unit": "game/mainProgram0800",
"line": "its row"
},
"376F": {
"unit": "game/mainProgram0800",
"line": "line walker start row"
},
"3771": {
"unit": "game/mainProgram0800",
"line": "set up the Bresenham walk; it returns X = 1 when the row is the major axis"
},
"3774": {
"unit": "game/mainProgram0800",
"line": "was the line mostly vertical?"
},
"3776": {
"unit": "game/mainProgram0800",
"line": "no, it is mostly horizontal -> report east or west"
},
"3778": {
"unit": "game/mainProgram0800",
"line": "facing 0 = north (up)"
},
"377A": {
"unit": "game/mainProgram0800",
"line": "row step is $01 (down) or $FF (up)"
},
"377C": {
"unit": "game/mainProgram0800",
"line": "stepping up -> north"
},
"377E": {
"unit": "game/mainProgram0800",
"line": "facing 1 = south (down)"
},
"3780": {
"unit": "game/mainProgram0800",
"line": "(always taken)"
},
"3782": {
"unit": "game/mainProgram0800",
"line": "facing 2 = east (right)"
},
"3784": {
"unit": "game/mainProgram0800",
"line": "column step is $01 (right) or $FF (left)"
},
"3786": {
"unit": "game/mainProgram0800",
"line": "stepping right -> east"
},
"3788": {
"unit": "game/mainProgram0800",
"line": "facing 3 = west (left)"
},
"378A": {
"unit": "game/mainProgram0800",
"line": "A = direction/facing code 0-3"
},
"378B": {
"unit": "game/mainProgram0800",
"routine": [
"setScanFilterOppositeSide - Configures the unit filter of collectUnitsInRadius so that only units of the side opposite to unit Y are collected, by writing the compare value and the branch opcode of the test at $37FE/$3800. A side 0 scanner (unit < 50) keeps the units >= 50 with a BCC, a side 1 scanner keeps the units < 50 with a BCS.",
"In: Y = the reference unit whose enemies are wanted",
"Out: scanSideFilterCompare+1 = $32 and scanSideFilterBranch = $90 (BCC) or $B0 (BCS); A clobbered",
"Called from: scanVisibleEnemies ($35D9), checkEnemyRecyclerSpotted ($3F64)"
],
"line": "unit 50 = the first unit of side 1"
},
"378D": {
"unit": "game/mainProgram0800",
"line": "patch the CMP operand of the scan filter"
},
"3790": {
"unit": "game/mainProgram0800",
"line": "$90 = BCC, i.e. skip everything below unit 50"
},
"3792": {
"unit": "game/mainProgram0800",
"line": "is the reference unit itself on side 0?"
},
"3794": {
"unit": "game/mainProgram0800",
"line": "yes -> keep the units of side 1"
},
"3796": {
"unit": "game/mainProgram0800",
"line": "side 1 scanner: $90 EOR $20 = $B0 = BCS, keep the units of side 0"
},
"3798": {
"unit": "game/mainProgram0800",
"line": "patch the branch opcode of the scan filter"
},
"379B": {
"unit": "game/mainProgram0800",
"line": "the filter is now set for the next collectUnitsInRadius"
},
"379C": {
"unit": "game/mainProgram0800",
"block": [
"Leftover of an older build: as code these two bytes are 'bcc $3798', the tail of an earlier",
"version of the side test. Overlay A's per-side patcher still stores its branch opcode here",
"($7BDB), where it now has no effect at all."
],
"line": "$90 $FA = the dead 'bcc $3798' of an earlier layout"
},
"379E": {
"unit": "game/mainProgram0800",
"routine": [
"collectAllUnitsInRadiusDead - Unreferenced alternate entry of collectUnitsInRadius that turns the side filter into 'accept every unit' (compare against unit 0 with a BCC) before falling into the scan. Nothing calls it - ageUnitVisibility ($357D) writes the same two bytes inline - so this is leftover code from an earlier build.",
"In: A = radius, X = centre column, Y = centre row",
"Out: dead code; it would leave scanSideFilterCompare+1 = 0 and scanSideFilterBranch = $90",
"Called from: nothing (dead code, rendered as .byte in the listing)"
],
"line": "keep the radius across the patching"
},
"379F": {
"unit": "game/mainProgram0800",
"line": "compare the unit index against 0 ..."
},
"37A1": {
"unit": "game/mainProgram0800",
"line": "-> the compare operand of the scan filter"
},
"37A4": {
"unit": "game/mainProgram0800",
"line": "... with a BCC, so no unit is ever skipped"
},
"37A6": {
"unit": "game/mainProgram0800",
"line": "-> the branch opcode of the scan filter"
},
"37A9": {
"unit": "game/mainProgram0800",
"line": "radius back into A and fall into the scan"
},
"37AA": {
"unit": "game/mainProgram0800",
"routine": [
"collectUnitsInRadius - Scans the square of half-width A around map cell (X,Y), clipped to the 40x40 battlefield, and lists every cell holding a unit that passes the side filter patched by setScanFilterOppositeSide: the unit index goes into $0400[n] and its squared distance from the centre into $0432[n]. When more than one unit was found, side 1 reverses the lists so that both machines break distance ties identically.",
"In: A = radius in cells, X = centre column, Y = centre row, playerSide $0B9F, the map at $F000",
"Out: candidate lists $0400/$0432, X = zp_29 = index of the last entry ($FF = nothing found); zp_22-zp_29 and zp_48/zp_49 used as scratch; operand scanReverseLimit+1 patched",
"Called from: scanVisibleEnemies ($3617), checkEnemyRecyclerSpotted ($3F72)"
],
"line": "keep the radius (zp_22 becomes the running column further down)"
},
"37AC": {
"unit": "game/mainProgram0800",
"line": "2 * radius"
},
"37AD": {
"unit": "game/mainProgram0800",
"line": "width of the scan square (the +1 comes at $37DA)"
},
"37AF": {
"unit": "game/mainProgram0800",
"line": "and its height"
},
"37B1": {
"unit": "game/mainProgram0800",
"line": "centre column = point B of distanceSquared"
},
"37B3": {
"unit": "game/mainProgram0800",
"line": "centre row"
},
"37B5": {
"unit": "game/mainProgram0800",
"line": "centre column back into A"
},
"37B6": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"37B7": {
"unit": "game/mainProgram0800",
"line": "leftmost column = centre - radius"
},
"37B9": {
"unit": "game/mainProgram0800",
"line": "still on the map -> use it"
},
"37BB": {
"unit": "game/mainProgram0800",
"line": "clipped at the left edge: the square still ends at centre + radius ..."
},
"37BC": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"37BD": {
"unit": "game/mainProgram0800",
"line": "centre + radius"
},
"37BF": {
"unit": "game/mainProgram0800",
"line": "... so that is all the width there is left"
},
"37C1": {
"unit": "game/mainProgram0800",
"line": "and the scan starts at column 0"
},
"37C3": {
"unit": "game/mainProgram0800",
"line": "first column of every scanned row"
},
"37C5": {
"unit": "game/mainProgram0800",
"line": "-> X for readMapCell"
},
"37C6": {
"unit": "game/mainProgram0800",
"line": "centre row back into A"
},
"37C7": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"37C8": {
"unit": "game/mainProgram0800",
"line": "topmost row = centre - radius (zp_22 still holds the radius)"
},
"37CA": {
"unit": "game/mainProgram0800",
"line": "still on the map -> use it"
},
"37CC": {
"unit": "game/mainProgram0800",
"line": "clipped at the top edge ..."
},
"37CD": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"37CE": {
"unit": "game/mainProgram0800",
"line": "centre + radius"
},
"37D0": {
"unit": "game/mainProgram0800",
"line": "... so there are fewer rows to scan"
},
"37D2": {
"unit": "game/mainProgram0800",
"line": "and the scan starts at row 0"
},
"37D4": {
"unit": "game/mainProgram0800",
"line": "row the scan starts on"
},
"37D6": {
"unit": "game/mainProgram0800",
"line": "-> Y for readMapCell"
},
"37D7": {
"unit": "game/mainProgram0800",
"line": "zp_48/49 = address of the top left cell of the square, Y = 0"
},
"37DA": {
"unit": "game/mainProgram0800",
"line": "width = 2*radius+1 cells"
},
"37DC": {
"unit": "game/mainProgram0800",
"line": "height = 2*radius+1 rows"
},
"37DE": {
"unit": "game/mainProgram0800",
"line": "$FF ..."
},
"37E0": {
"unit": "game/mainProgram0800",
"line": "... = the candidate list is still empty"
},
"37E2": {
"unit": "game/mainProgram0800",
"block": [
"Row loop: one map row of the square per pass; zp_48/49 walks down the map 40 bytes at a time."
],
"line": "row about to be scanned"
},
"37E4": {
"unit": "game/mainProgram0800",
"line": "negative row -> nothing to do here"
},
"37E6": {
"unit": "game/mainProgram0800",
"line": "the battlefield is 40 rows deep"
},
"37E8": {
"unit": "game/mainProgram0800",
"line": "past the bottom edge -> skip the row"
},
"37EA": {
"unit": "game/mainProgram0800",
"line": "Y = offset from the start of the map row"
},
"37EC": {
"unit": "game/mainProgram0800",
"line": "first column of the square"
},
"37EE": {
"unit": "game/mainProgram0800",
"line": "restart at the left edge of the square"
},
"37F0": {
"unit": "game/mainProgram0800",
"block": [
"Column loop over one row of the square."
],
"line": "column about to be tested"
},
"37F2": {
"unit": "game/mainProgram0800",
"line": "off the left edge of the map"
},
"37F4": {
"unit": "game/mainProgram0800",
"line": "40 columns"
},
"37F6": {
"unit": "game/mainProgram0800",
"line": "off the right edge"
},
"37F8": {
"unit": "game/mainProgram0800",
"line": "the map byte of this cell"
},
"37FA": {
"unit": "game/mainProgram0800",
"line": "bit 7 clear = plain terrain, no unit here"
},
"37FC": {
"unit": "game/mainProgram0800",
"line": "bit 7 is the 'unit here' marker; the rest is the unit index 0-99"
},
"37FE": {
"unit": "game/mainProgram0800",
"line": "operand patched by setScanFilterOppositeSide ($32) or by the dead entry above (0)"
},
"3800": {
"unit": "game/mainProgram0800",
"line": "opcode patched BCC/BCS: drop the units of the unwanted side"
},
"3802": {
"unit": "game/mainProgram0800",
"line": "one more candidate"
},
"3804": {
"unit": "game/mainProgram0800",
"line": "its slot number"
},
"3806": {
"unit": "game/mainProgram0800",
"line": "unit index -> the $0400 candidate list"
},
"3809": {
"unit": "game/mainProgram0800",
"line": "dist^2 between this cell (zp_22/zp_24) and the centre (zp_27/zp_28)"
},
"380C": {
"unit": "game/mainProgram0800",
"line": "same slot"
},
"380E": {
"unit": "game/mainProgram0800",
"line": "-> the parallel $0432 distance list"
},
"3811": {
"unit": "game/mainProgram0800",
"line": "next column"
},
"3813": {
"unit": "game/mainProgram0800",
"line": "next byte of the map row"
},
"3814": {
"unit": "game/mainProgram0800",
"line": "whole width of the square done?"
},
"3816": {
"unit": "game/mainProgram0800",
"line": "no -> next cell"
},
"3818": {
"unit": "game/mainProgram0800",
"block": [
"End of a row: step the map pointer down to the next one."
],
"line": "map row pointer, low byte"
},
"381A": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"381B": {
"unit": "game/mainProgram0800",
"line": "40 bytes per map row"
},
"381D": {
"unit": "game/mainProgram0800",
"line": "store it back"
},
"381F": {
"unit": "game/mainProgram0800",
"line": "high byte"
},
"3821": {
"unit": "game/mainProgram0800",
"line": "propagate the carry"
},
"3823": {
"unit": "game/mainProgram0800",
"line": "zp_48/49 now points at the next row of the square"
},
"3825": {
"unit": "game/mainProgram0800",
"line": "next row number"
},
"3827": {
"unit": "game/mainProgram0800",
"line": "one row less to do"
},
"3829": {
"unit": "game/mainProgram0800",
"line": "until the whole square has been scanned"
},
"382B": {
"unit": "game/mainProgram0800",
"block": [
"Side 1 reverses both lists. The two machines see the battlefield mirrored and therefore scan",
"the square in opposite directions; reversing makes them pick the same unit whenever two",
"candidates tie on distance."
],
"line": "index of the last entry"
},
"382D": {
"unit": "game/mainProgram0800",
"line": "nothing found at all"
},
"382F": {
"unit": "game/mainProgram0800",
"line": "only one entry - nothing to reverse"
},
"3831": {
"unit": "game/mainProgram0800",
"line": "0 = the local player is side 0, 1 = side 1"
},
"3834": {
"unit": "game/mainProgram0800",
"line": "side 0 keeps the natural scan order"
},
"3836": {
"unit": "game/mainProgram0800",
"line": "Y walks up from the front while X walks down from the back"
},
"3838": {
"unit": "game/mainProgram0800",
"line": "swap the two unit slots ..."
},
"383B": {
"unit": "game/mainProgram0800",
"line": "keep the front entry"
},
"383C": {
"unit": "game/mainProgram0800",
"line": "back entry"
},
"383F": {
"unit": "game/mainProgram0800",
"line": "-> to the front"
},
"3842": {
"unit": "game/mainProgram0800",
"line": "front entry back off the stack"
},
"3843": {
"unit": "game/mainProgram0800",
"line": "-> to the back"
},
"3846": {
"unit": "game/mainProgram0800",
"line": "... and the two matching distances"
},
"3849": {
"unit": "game/mainProgram0800",
"line": "keep the front distance"
},
"384A": {
"unit": "game/mainProgram0800",
"line": "back distance"
},
"384D": {
"unit": "game/mainProgram0800",
"line": "-> to the front"
},
"3850": {
"unit": "game/mainProgram0800",
"line": "front distance back off the stack"
},
"3851": {
"unit": "game/mainProgram0800",
"line": "-> to the back"
},
"3854": {
"unit": "game/mainProgram0800",
"line": "next pair from the front"
},
"3855": {
"unit": "game/mainProgram0800",
"line": "next pair from the back"
},
"3856": {
"unit": "game/mainProgram0800",
"line": "patch the loop limit with the index that is walking down"
},
"3859": {
"unit": "game/mainProgram0800",
"line": "operand = X"
},
"385B": {
"unit": "game/mainProgram0800",
"line": "keep swapping until the two indexes meet"
},
"385D": {
"unit": "game/mainProgram0800",
"line": "X = index of the last entry, $FF when nothing was found"
},
"385F": {
"unit": "game/mainProgram0800",
"line": "back to the caller"
},
"3860": {
"unit": "game/mainProgram0800",
"block": [
"squaresTable - n*n for n = 0..15. distanceSquared uses it forwards; cellDistance in the",
"opponent module ($E832) and the map generator ($7831) use it backwards as a square root."
],
"line": "0,1,4,9,16,25,36,49 = n*n for n = 0-7"
},
"3868": {
"unit": "game/mainProgram0800",
"line": "64,81,100,121,144,169,196,225 = n*n for n = 8-15"
},
"3870": {
"unit": "game/mainProgram0800",
"routine": [
"distanceSquared - Squared distance between two map cells: |dCol|^2 + |dRow|^2 taken from squaresTable. Each delta is clamped to 15 cells and the sum saturates at $FF, which makes it a usable range metric everywhere on the 40x40 map without a square root.",
"In: zp_22/zp_24 = column/row of the cell, zp_27/zp_28 = column/row of the reference point",
"Out: A = squared distance ($FF when it overflows); X clobbered",
"Called from: collectUnitsInRadius ($3809), the target cursor range check ($2159), cellDistance in the opponent module ($E82A)"
],
"line": "column of the cell"
},
"3872": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"3873": {
"unit": "game/mainProgram0800",
"line": "minus the reference column"
},
"3875": {
"unit": "game/mainProgram0800",
"line": "already positive"
},
"3877": {
"unit": "game/mainProgram0800",
"line": "negate to get |dCol| ..."
},
"3879": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"387A": {
"unit": "game/mainProgram0800",
"line": "... two's complement"
},
"387C": {
"unit": "game/mainProgram0800",
"line": "-> table index"
},
"387D": {
"unit": "game/mainProgram0800",
"line": "squaresTable only covers 0-15"
},
"387F": {
"unit": "game/mainProgram0800",
"line": "in range"
},
"3881": {
"unit": "game/mainProgram0800",
"line": "clamp the distance at 15 cells"
},
"3883": {
"unit": "game/mainProgram0800",
"line": "dCol^2"
},
"3886": {
"unit": "game/mainProgram0800",
"line": "hold on to it while the row delta is worked out"
},
"3887": {
"unit": "game/mainProgram0800",
"line": "row of the cell"
},
"3889": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"388A": {
"unit": "game/mainProgram0800",
"line": "minus the reference row"
},
"388C": {
"unit": "game/mainProgram0800",
"line": "already positive"
},
"388E": {
"unit": "game/mainProgram0800",
"line": "negate to get |dRow| ..."
},
"3890": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"3891": {
"unit": "game/mainProgram0800",
"line": "... two's complement"
},
"3893": {
"unit": "game/mainProgram0800",
"line": "-> table index"
},
"3894": {
"unit": "game/mainProgram0800",
"line": "again only 0-15 are tabulated"
},
"3896": {
"unit": "game/mainProgram0800",
"line": "in range"
},
"3898": {
"unit": "game/mainProgram0800",
"line": "clamp at 15 cells"
},
"389A": {
"unit": "game/mainProgram0800",
"line": "dCol^2 back off the stack"
},
"389B": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"389C": {
"unit": "game/mainProgram0800",
"line": "+ dRow^2"
},
"389F": {
"unit": "game/mainProgram0800",
"line": "no overflow"
},
"38A1": {
"unit": "game/mainProgram0800",
"line": "saturate at $FF instead of wrapping round"
},
"38A3": {
"unit": "game/mainProgram0800",
"line": "A = dCol^2 + dRow^2"
},
"38A4": {
"unit": "game/mainProgram0800",
"block": [
"baseDamageTable - base damage of one shot: 3 rows of 5 bytes, row = attacker type (0 GRUNT,",
"1 RIDER, 2 BOOMER; spies and comcens never fire) and column = target type (0 GRUNT, 1 RIDER,",
"2 BOOMER, 3 SPY, 4 COMCEN). Read as baseDamageTable[attackerRow*5 + targetType] at $38F4."
],
"line": "GRUNT vs grunt 4, rider 5, boomer 25, spy 8, comcen 12; RIDER vs grunt 3, rider 4, boomer 20"
},
"38AC": {
"unit": "game/mainProgram0800",
"line": "RIDER vs spy 6, comcen 8; BOOMER vs grunt 12, rider 16, boomer 8, spy 8, comcen 20"
},
"38B3": {
"unit": "game/mainProgram0800",
"block": [
"terrainHeightTable - height (cover level) of each terrain class 0-7: class 0 blank, 1 river and",
"2 forest are flat, 3 lower contour = 1, 4 hill = 2, 5 upper contour = 3, 6 summit = 4,",
"7 recycler = 0. Used by the line-of-sight ridge test and by the combat height bonus."
],
"line": "heights of terrain classes 0-7: 0,0,0,1,2,3,4,0"
},
"38BB": {
"unit": "game/mainProgram0800",
"block": [
"combatScratch - the seven work bytes of resolveAttack, held inside the code: target unit,",
"attacker unit, the running damage total, and the four map coordinates of the shot. The",
"coordinates have to be saved because overlay B's drawShotLineInViewport destroys zp_92-zp_95."
],
"line": "the unit being shot at"
},
"38BC": {
"unit": "game/mainProgram0800",
"line": "the unit doing the shooting"
},
"38BD": {
"unit": "game/mainProgram0800",
"line": "running damage total; half of it is what actually gets applied"
},
"38BE": {
"unit": "game/mainProgram0800",
"line": "attacker's map row"
},
"38BF": {
"unit": "game/mainProgram0800",
"line": "attacker's map column"
},
"38C0": {
"unit": "game/mainProgram0800",
"line": "target's map row"
},
"38C1": {
"unit": "game/mainProgram0800",
"line": "target's map column"
},
"38C2": {
"unit": "game/mainProgram0800",
"routine": [
"resolveAttack - Resolves one shot of unit X at unit Y. Only GRUNT/RIDER/BOOMER (types 0-2) carry a weapon. The damage starts at baseDamageTable[attacker][target] and is then modified: a blitzing attacker that has built up a charge ($F708 bit 7) throws up to 15 energy (5 for a BOOMER) into the shot and pays 3/4 of that itself; +2 when the attacker is dug in and -25% when the target is; plus the height difference of the two cells; +10 when the target stands in a river (class 1) and +2 on a contour (class 3 or 5); and for non-BOOMERs +20 when the target faces the same way (shot in the back) or +10 for a flank shot. A zero or negative total becomes 2, and half of the total is applied through applyHitToUnit. If the battlefield screen is up and the comcen display is alive the shot is drawn both in the zoom window and on the overview map, then the shot or kill sound plays.",
"In: X = attacker unit, Y = target unit; the unit record arrays, terrain classes $077E, currentScreen $90FB, comcenStunStatus $922A",
"Out: target damaged via applyHitToUnit, the attacker may lose energy, attackerType $923D, $38BB-$38C1 scratch, shot graphics (sprite 3 and the zoom window) and a sound; the operands loadShotSoundId+1, loadChargeEnergySpent+1 and subtractDugInDiscount+1 are patched",
"Called from: the per-unit update loop ($44A0)"
],
"line": "remember the attacker"
},
"38C5": {
"unit": "game/mainProgram0800",
"line": "and the target"
},
"38C8": {
"unit": "game/mainProgram0800",
"line": "no offset yet ..."
},
"38CA": {
"unit": "game/mainProgram0800",
"line": "row offset into baseDamageTable (5 entries per attacker type)"
},
"38CC": {
"unit": "game/mainProgram0800",
"line": "attacker's type byte"
},
"38CF": {
"unit": "game/mainProgram0800",
"line": "0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN"
},
"38D1": {
"unit": "game/mainProgram0800",
"line": "$923D, also read by both shot-drawing routines"
},
"38D4": {
"unit": "game/mainProgram0800",
"line": "types 3 and 4 have no weapon"
},
"38D6": {
"unit": "game/mainProgram0800",
"line": "only grunts, riders and boomers shoot"
},
"38D8": {
"unit": "game/mainProgram0800",
"line": "a spy or comcen never fires - nothing happens at all"
},
"38DB": {
"unit": "game/mainProgram0800",
"block": [
"Pick the row of baseDamageTable that belongs to the attacker's type."
],
"line": "BOOMER?"
},
"38DD": {
"unit": "game/mainProgram0800",
"line": "no"
},
"38DF": {
"unit": "game/mainProgram0800",
"line": "boomer row = table entries 10-14"
},
"38E1": {
"unit": "game/mainProgram0800",
"line": "-> row offset 10"
},
"38E3": {
"unit": "game/mainProgram0800",
"line": "RIDER?"
},
"38E5": {
"unit": "game/mainProgram0800",
"line": "no, so it is a GRUNT and the offset stays 0"
},
"38E7": {
"unit": "game/mainProgram0800",
"line": "rider row = table entries 5-9"
},
"38E9": {
"unit": "game/mainProgram0800",
"line": "-> row offset 5"
},
"38EB": {
"unit": "game/mainProgram0800",
"line": "target's type byte"
},
"38EE": {
"unit": "game/mainProgram0800",
"line": "type 0-4 selects the column"
},
"38F0": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"38F1": {
"unit": "game/mainProgram0800",
"line": "+ the attacker's row offset"
},
"38F3": {
"unit": "game/mainProgram0800",
"line": "-> table index"
},
"38F4": {
"unit": "game/mainProgram0800",
"line": "base damage for this attacker/target pairing"
},
"38F7": {
"unit": "game/mainProgram0800",
"line": "start the running damage total"
},
"38FA": {
"unit": "game/mainProgram0800",
"line": "sound 10 = ordinary shot"
},
"38FC": {
"unit": "game/mainProgram0800",
"line": "attacker type"
},
"38FF": {
"unit": "game/mainProgram0800",
"line": "BOOMER?"
},
"3901": {
"unit": "game/mainProgram0800",
"line": "no -> keep sound 10"
},
"3903": {
"unit": "game/mainProgram0800",
"line": "sound 11 = boomer shell"
},
"3905": {
"unit": "game/mainProgram0800",
"line": "patch the LDA # of the sound played when the shot is over"
},
"3908": {
"unit": "game/mainProgram0800",
"block": [
"Blitz charge: a unit with blitz switched on ($F8FC bit 7) collects a one-shot charge flag in",
"$F708 bit 7 while it moves ($3EE2). Firing spends the charge, adding energy to the damage and",
"taking 3/4 of that energy off the attacker itself."
],
"line": "the attacker"
},
"390B": {
"unit": "game/mainProgram0800",
"line": "$F8FC bit 7 = blitz is switched on"
},
"390E": {
"unit": "game/mainProgram0800",
"line": "not blitzing -> no charge bonus"
},
"3910": {
"unit": "game/mainProgram0800",
"line": "$F708 bit 7 = a charge was built up by moving"
},
"3913": {
"unit": "game/mainProgram0800",
"line": "nothing charged up"
},
"3915": {
"unit": "game/mainProgram0800",
"line": "clear the charge bit ..."
},
"3917": {
"unit": "game/mainProgram0800",
"line": "... it is used up by this shot"
},
"391A": {
"unit": "game/mainProgram0800",
"line": "up to 15 energy may be thrown into the charge"
},
"391C": {
"unit": "game/mainProgram0800",
"line": "which damage row is in use?"
},
"391E": {
"unit": "game/mainProgram0800",
"line": "row offset 10 = BOOMER"
},
"3920": {
"unit": "game/mainProgram0800",
"line": "grunt or rider"
},
"3922": {
"unit": "game/mainProgram0800",
"line": "a boomer only spends 5"
},
"3924": {
"unit": "game/mainProgram0800",
"line": "the amount we would like to spend"
},
"3926": {
"unit": "game/mainProgram0800",
"line": "attacker's energy byte"
},
"3929": {
"unit": "game/mainProgram0800",
"line": "bits 0-5 = energy 0-50, bits 6-7 are the visibility flags"
},
"392B": {
"unit": "game/mainProgram0800",
"line": "enough energy for the whole charge?"
},
"392D": {
"unit": "game/mainProgram0800",
"line": "no - it cannot spend more than it has"
},
"392F": {
"unit": "game/mainProgram0800",
"line": "yes - spend the full amount"
},
"3931": {
"unit": "game/mainProgram0800",
"line": "patch the amount spent into the LDA # below"
},
"3934": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"3935": {
"unit": "game/mainProgram0800",
"line": "the running damage total"
},
"3938": {
"unit": "game/mainProgram0800",
"line": "the whole amount is added to the damage"
},
"393B": {
"unit": "game/mainProgram0800",
"line": "the attacker itself pays for it"
},
"393E": {
"unit": "game/mainProgram0800",
"line": "operand = the energy just spent"
},
"3940": {
"unit": "game/mainProgram0800",
"line": "/2"
},
"3941": {
"unit": "game/mainProgram0800",
"line": "keep half of it"
},
"3943": {
"unit": "game/mainProgram0800",
"line": "/4"
},
"3944": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"3945": {
"unit": "game/mainProgram0800",
"line": "1/2 + 1/4 = 3/4 of the amount spent"
},
"3947": {
"unit": "game/mainProgram0800",
"line": "take that off the attacker's own energy"
},
"394A": {
"unit": "game/mainProgram0800",
"line": "Y back to the target"
},
"394D": {
"unit": "game/mainProgram0800",
"line": "X back to the attacker"
},
"3950": {
"unit": "game/mainProgram0800",
"block": [
"Dug-in modifiers: $F76C bit 6 marks a unit that has finished digging in."
],
"line": "attacker's type byte"
},
"3953": {
"unit": "game/mainProgram0800",
"line": "bit 6 = dug in / fortified"
},
"3955": {
"unit": "game/mainProgram0800",
"line": "not dug in"
},
"3957": {
"unit": "game/mainProgram0800",
"line": "+1 ..."
},
"395A": {
"unit": "game/mainProgram0800",
"line": "... +1 = +2 damage when firing from a prepared position"
},
"395D": {
"unit": "game/mainProgram0800",
"line": "target's type byte"
},
"3960": {
"unit": "game/mainProgram0800",
"line": "bit 6 = the target is dug in"
},
"3962": {
"unit": "game/mainProgram0800",
"line": "not dug in"
},
"3964": {
"unit": "game/mainProgram0800",
"line": "running damage total"
},
"3967": {
"unit": "game/mainProgram0800",
"line": "/2"
},
"3968": {
"unit": "game/mainProgram0800",
"line": "/4"
},
"3969": {
"unit": "game/mainProgram0800",
"line": "patch the subtraction below with damage/4"
},
"396C": {
"unit": "game/mainProgram0800",
"line": "the total again"
},
"396F": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"3970": {
"unit": "game/mainProgram0800",
"line": "operand = damage/4: a dug-in target takes 25% less"
},
"3972": {
"unit": "game/mainProgram0800",
"line": "the reduced total"
},
"3975": {
"unit": "game/mainProgram0800",
"block": [
"Terrain modifiers: the height difference between the two cells, plus a bonus for a target",
"caught in the open (a river) or on a slope."
],
"line": "map byte hidden under the target"
},
"3978": {
"unit": "game/mainProgram0800",
"line": "-> terrain class 0-8"
},
"397B": {
"unit": "game/mainProgram0800",
"line": "keep the target's terrain class"
},
"397D": {
"unit": "game/mainProgram0800",
"line": "-> height table index"
},
"397E": {
"unit": "game/mainProgram0800",
"line": "height of the target's cell"
},
"3981": {
"unit": "game/mainProgram0800",
"line": "keep it for the difference below"
},
"3983": {
"unit": "game/mainProgram0800",
"line": "the attacker"
},
"3986": {
"unit": "game/mainProgram0800",
"line": "map byte hidden under the attacker"
},
"3989": {
"unit": "game/mainProgram0800",
"line": "-> terrain class"
},
"398C": {
"unit": "game/mainProgram0800",
"line": "stored but never read again - a dead store"
},
"398E": {
"unit": "game/mainProgram0800",
"line": "-> height table index"
},
"398F": {
"unit": "game/mainProgram0800",
"line": "height of the attacker's cell"
},
"3992": {
"unit": "game/mainProgram0800",
"line": "Y back to the target unit"
},
"3995": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"3996": {
"unit": "game/mainProgram0800",
"line": "attacker height - target height"
},
"3998": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"3999": {
"unit": "game/mainProgram0800",
"line": "+ the running total"
},
"399C": {
"unit": "game/mainProgram0800",
"line": "firing downhill adds, firing uphill subtracts"
},
"399F": {
"unit": "game/mainProgram0800",
"line": "terrain class the target is standing on"
},
"39A1": {
"unit": "game/mainProgram0800",
"line": "class 1 = river"
},
"39A3": {
"unit": "game/mainProgram0800",
"line": "not a river"
},
"39A5": {
"unit": "game/mainProgram0800",
"line": "running damage total"
},
"39A8": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"39A9": {
"unit": "game/mainProgram0800",
"line": "+10: a target in the water has no cover at all"
},
"39AB": {
"unit": "game/mainProgram0800",
"line": "store the total again"
},
"39AE": {
"unit": "game/mainProgram0800",
"line": "the river bonus and the slope bonus are exclusive"
},
"39B1": {
"unit": "game/mainProgram0800",
"line": "class 3 = lower contour"
},
"39B3": {
"unit": "game/mainProgram0800",
"line": "yes"
},
"39B5": {
"unit": "game/mainProgram0800",
"line": "class 5 = upper contour"
},
"39B7": {
"unit": "game/mainProgram0800",
"line": "any other class gives no bonus"
},
"39B9": {
"unit": "game/mainProgram0800",
"line": "+1 ..."
},
"39BC": {
"unit": "game/mainProgram0800",
"line": "... +1 = +2 for a target caught on an open slope"
},
"39BF": {
"unit": "game/mainProgram0800",
"block": [
"Facing bonus. Facing codes are 0 north, 1 south, 2 east, 3 west, so EOR 1 gives the opposite",
"direction: the same facing means the target has its back to us, the opposite facing means it",
"is looking straight at us, and the other two are a flank shot. Boomers lob shells and get no",
"bonus at all."
],
"line": "attacker type"
},
"39C2": {
"unit": "game/mainProgram0800",
"line": "BOOMER?"
},
"39C4": {
"unit": "game/mainProgram0800",
"line": "artillery gets no facing bonus"
},
"39C6": {
"unit": "game/mainProgram0800",
"line": "attacker's facing, bits 0-1 of $F708"
},
"39C9": {
"unit": "game/mainProgram0800",
"line": "facing bits only"
},
"39CB": {
"unit": "game/mainProgram0800",
"line": "keep it"
},
"39CD": {
"unit": "game/mainProgram0800",
"line": "target's facing"
},
"39D0": {
"unit": "game/mainProgram0800",
"line": "facing bits only"
},
"39D2": {
"unit": "game/mainProgram0800",
"line": "both looking the same way?"
},
"39D4": {
"unit": "game/mainProgram0800",
"line": "no"
},
"39D6": {
"unit": "game/mainProgram0800",
"line": "running damage total"
},
"39D9": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"39DA": {
"unit": "game/mainProgram0800",
"line": "+20: the target has its back to the shooter"
},
"39DC": {
"unit": "game/mainProgram0800",
"line": "store the total again"
},
"39DF": {
"unit": "game/mainProgram0800",
"line": "(always taken, the total cannot be 0 here)"
},
"39E1": {
"unit": "game/mainProgram0800",
"line": "the facing opposite to the target's"
},
"39E3": {
"unit": "game/mainProgram0800",
"line": "is the target looking straight at the shooter?"
},
"39E5": {
"unit": "game/mainProgram0800",
"line": "head on -> no bonus"
},
"39E7": {
"unit": "game/mainProgram0800",
"line": "the remaining two facings are a flank shot"
},
"39EA": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"39EB": {
"unit": "game/mainProgram0800",
"line": "+10"
},
"39ED": {
"unit": "game/mainProgram0800",
"line": "store the total again"
},
"39F0": {
"unit": "game/mainProgram0800",
"block": [
"Apply the shot."
],
"line": "final total"
},
"39F3": {
"unit": "game/mainProgram0800",
"line": "the modifiers can drive it below zero"
},
"39F5": {
"unit": "game/mainProgram0800",
"line": "or exactly to zero"
},
"39F7": {
"unit": "game/mainProgram0800",
"line": "every hit does at least 2 points (before halving)"
},
"39F9": {
"unit": "game/mainProgram0800",
"line": "the target unit"
},
"39FC": {
"unit": "game/mainProgram0800",
"line": "only half of the accumulated total is really applied"
},
"39FD": {
"unit": "game/mainProgram0800",
"line": "damage the target, score it and maybe end the game"
},
"3A00": {
"unit": "game/mainProgram0800",
"block": [
"Draw the shot, but only while the player is actually looking at the battlefield and the comcen",
"display is alive. Overlay B draws it as fat pixels in the 7x5 zoom window and",
"drawShotLineSprite draws the same shot on the 4-pixel-per-cell overview map."
],
"line": "0 = the battlefield screen (F1) is the one on show"
},
"3A03": {
"unit": "game/mainProgram0800",
"line": "radar, drone or menu screen -> no shot graphics"
},
"3A05": {
"unit": "game/mainProgram0800",
"line": "$922A = $FF (so bit 6 is set) while the comcen is knocked out"
},
"3A08": {
"unit": "game/mainProgram0800",
"line": "blacked-out display -> draw nothing"
},
"3A0A": {
"unit": "game/mainProgram0800",
"line": "the target"
},
"3A0D": {
"unit": "game/mainProgram0800",
"line": "its column"
},
"3A10": {
"unit": "game/mainProgram0800",
"line": "saved, because the overlay routine destroys zp_92-zp_95"
},
"3A13": {
"unit": "game/mainProgram0800",
"line": "line walker target column"
},
"3A15": {
"unit": "game/mainProgram0800",
"line": "its row"
},
"3A18": {
"unit": "game/mainProgram0800",
"line": "saved copy"
},
"3A1B": {
"unit": "game/mainProgram0800",
"line": "line walker target row"
},
"3A1D": {
"unit": "game/mainProgram0800",
"line": "the attacker"
},
"3A20": {
"unit": "game/mainProgram0800",
"line": "its column"
},
"3A23": {
"unit": "game/mainProgram0800",
"line": "saved copy"
},
"3A26": {
"unit": "game/mainProgram0800",
"line": "line walker start column"
},
"3A28": {
"unit": "game/mainProgram0800",
"line": "its row"
},
"3A2B": {
"unit": "game/mainProgram0800",
"line": "saved copy"
},
"3A2E": {
"unit": "game/mainProgram0800",
"line": "line walker start row"
},
"3A30": {
"unit": "game/mainProgram0800",
"line": "overlay B: white fat-pixel shot line in the 7x5 zoom window"
},
"3A33": {
"unit": "game/mainProgram0800",
"line": "put the four endpoints back for the sprite version"
},
"3A36": {
"unit": "game/mainProgram0800",
"line": "target row"
},
"3A38": {
"unit": "game/mainProgram0800",
"line": "saved target column"
},
"3A3B": {
"unit": "game/mainProgram0800",
"line": "target column"
},
"3A3D": {
"unit": "game/mainProgram0800",
"line": "saved attacker row"
},
"3A40": {
"unit": "game/mainProgram0800",
"line": "attacker row"
},
"3A42": {
"unit": "game/mainProgram0800",
"line": "saved attacker column"
},
"3A45": {
"unit": "game/mainProgram0800",
"line": "attacker column"
},
"3A47": {
"unit": "game/mainProgram0800",
"line": "and draw the same shot on the small overview map"
},
"3A4A": {
"unit": "game/mainProgram0800",
"block": [
"Sound: a kill overrides the shot sound patched in at $3905."
],
"line": "the target"
},
"3A4D": {
"unit": "game/mainProgram0800",
"line": "operand = sound 10 (shot) or 11 (boomer shell), patched at $3905"
},
"3A4F": {
"unit": "game/mainProgram0800",
"line": "bit 7 of the type byte = the target was destroyed"
},
"3A52": {
"unit": "game/mainProgram0800",
"line": "still alive -> ordinary shot sound"
},
"3A54": {
"unit": "game/mainProgram0800",
"line": "sound 9 = kill"
},
"3A56": {
"unit": "game/mainProgram0800",
"line": "queue it"
},
"3A59": {
"unit": "game/mainProgram0800",
"line": "shared exit, also used by the 'spy/comcen cannot fire' case"
},
"3A5A": {
"unit": "game/mainProgram0800",
"routine": [
"drawShotLineSprite - Draws the shot on the 4-pixel-per-cell overview map using hardware sprite 3. Both cells are converted to sprite pixel coordinates; because a sprite only covers 24x21 pixels the start point is walked toward the target until the rest of the line fits that box. Sprite 3 is then placed on the top left corner of the box (+2 pixels so the line sits in the middle of the 4x4 cell) in white, the segment is plotted into the sprite shape (dotted for a BOOMER) with a small diamond at the impact end, and shotLineTimer gives the IRQ 8 frames before it hides the sprite again.",
"In: zp_92/zp_93 = attacker row/column, zp_94/zp_95 = target row/column (map cells), attackerType $923D",
"Out: sprite 3 shape at $8AC0, position $9013/$901B, colour $900B = 1 (white), bit 3 of the sprite enable shadow $9022, shotLineTimer $91C8 = 8; zp_48 and the whole line walker state clobbered",
"Called from: resolveAttack ($3A47)"
],
"block": [
"Hide sprite 3 and wipe its shape before rebuilding it."
],
"line": "shadow of VIC_SPR_ENA $D015"
},
"3A5D": {
"unit": "game/mainProgram0800",
"line": "clear bit 3: sprite 3 off while its shape is rebuilt"
},
"3A5F": {
"unit": "game/mainProgram0800",
"line": "write the shadow back"
},
"3A62": {
"unit": "game/mainProgram0800",
"line": "64 bytes of sprite data"
},
"3A64": {
"unit": "game/mainProgram0800",
"line": "blank byte"
},
"3A66": {
"unit": "game/mainProgram0800",
"line": "clear sprite 3's shape at $8AC0"
},
"3A69": {
"unit": "game/mainProgram0800",
"line": "next byte"
},
"3A6A": {
"unit": "game/mainProgram0800",
"line": "until all 64 are cleared"
},
"3A6C": {
"unit": "game/mainProgram0800",
"block": [
"Convert both map cells into sprite pixel coordinates (column*4+$1F, row*4+$39)."
],
"line": "target column"
},
"3A6E": {
"unit": "game/mainProgram0800",
"line": "target row"
},
"3A70": {
"unit": "game/mainProgram0800",
"line": "-> sprite pixel position on the overview map"
},
"3A73": {
"unit": "game/mainProgram0800",
"line": "from here on the line walker works in pixels, not cells"
},
"3A75": {
"unit": "game/mainProgram0800",
"line": "in sprite pixels as well"
},
"3A77": {
"unit": "game/mainProgram0800",
"line": "attacker column"
},
"3A79": {
"unit": "game/mainProgram0800",
"line": "attacker row"
},
"3A7B": {
"unit": "game/mainProgram0800",
"line": "-> sprite pixel position"
},
"3A7E": {
"unit": "game/mainProgram0800",
"line": "line walker start, in pixels"
},
"3A80": {
"unit": "game/mainProgram0800",
"line": "in sprite pixels as well"
},
"3A82": {
"unit": "game/mainProgram0800",
"line": "set up the Bresenham walk over the pixel line"
},
"3A85": {
"unit": "game/mainProgram0800",
"line": "step counter ..."
},
"3A87": {
"unit": "game/mainProgram0800",
"line": "counts the pixels dropped off the far end - nothing ever reads it (leftover)"
},
"3A89": {
"unit": "game/mainProgram0800",
"block": [
"A sprite is only 24x21 pixels, so walk the start point toward the target until what is left of",
"the line fits inside one sprite; the tail nearest the shooter simply is not drawn."
],
"line": "how tall is the remaining segment?"
},
"3A8B": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"3A8C": {
"unit": "game/mainProgram0800",
"line": "end row - current row"
},
"3A8E": {
"unit": "game/mainProgram0800",
"line": "positive already"
},
"3A90": {
"unit": "game/mainProgram0800",
"line": "|dy| ..."
},
"3A92": {
"unit": "game/mainProgram0800",
"line": "... two's complement"
},
"3A94": {
"unit": "game/mainProgram0800",
"line": "a sprite is 21 pixel rows tall"
},
"3A96": {
"unit": "game/mainProgram0800",
"line": "still too tall -> drop one more step"
},
"3A98": {
"unit": "game/mainProgram0800",
"line": "how wide is it?"
},
"3A9A": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"3A9B": {
"unit": "game/mainProgram0800",
"line": "end column - current column"
},
"3A9D": {
"unit": "game/mainProgram0800",
"line": "positive already"
},
"3A9F": {
"unit": "game/mainProgram0800",
"line": "|dx| ..."
},
"3AA1": {
"unit": "game/mainProgram0800",
"line": "... two's complement"
},
"3AA3": {
"unit": "game/mainProgram0800",
"line": "a sprite is 24 pixel columns wide"
},
"3AA5": {
"unit": "game/mainProgram0800",
"line": "the rest of the line fits -> draw it"
},
"3AA7": {
"unit": "game/mainProgram0800",
"line": "walk one pixel closer to the target"
},
"3AAA": {
"unit": "game/mainProgram0800",
"line": "the whole line has been consumed"
},
"3AAC": {
"unit": "game/mainProgram0800",
"line": "count the dropped step (dead store)"
},
"3AAE": {
"unit": "game/mainProgram0800",
"line": "test the shortened line again"
},
"3AB1": {
"unit": "game/mainProgram0800",
"block": [
"Put sprite 3 on the top left corner of the remaining segment."
],
"line": "assume the current point is the left edge"
},
"3AB3": {
"unit": "game/mainProgram0800",
"line": "which way does the walk run?"
},
"3AB5": {
"unit": "game/mainProgram0800",
"line": "to the right -> the current point really is leftmost"
},
"3AB7": {
"unit": "game/mainProgram0800",
"line": "to the left -> the target end is leftmost"
},
"3AB9": {
"unit": "game/mainProgram0800",
"line": "same for the top edge"
},
"3ABB": {
"unit": "game/mainProgram0800",
"line": "which way vertically?"
},
"3ABD": {
"unit": "game/mainProgram0800",
"line": "downwards -> the current point is topmost"
},
"3ABF": {
"unit": "game/mainProgram0800",
"line": "upwards -> the target end is topmost"
},
"3AC1": {
"unit": "game/mainProgram0800",
"line": "a perfectly vertical line?"
},
"3AC3": {
"unit": "game/mainProgram0800",
"line": "no"
},
"3AC5": {
"unit": "game/mainProgram0800",
"line": "shift the sprite one pixel left so the line is not on its edge column"
},
"3AC6": {
"unit": "game/mainProgram0800",
"line": "a perfectly horizontal line?"
},
"3AC8": {
"unit": "game/mainProgram0800",
"line": "no"
},
"3ACA": {
"unit": "game/mainProgram0800",
"line": "shift it one pixel up"
},
"3ACB": {
"unit": "game/mainProgram0800",
"line": "$9013 = shadow of sprite 3's X position"
},
"3ACE": {
"unit": "game/mainProgram0800",
"line": "$901B = shadow of sprite 3's Y position"
},
"3AD1": {
"unit": "game/mainProgram0800",
"block": [
"Make both endpoints relative to the sprite origin, then centre the sprite in the map cell."
],
"line": "line start column"
},
"3AD3": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"3AD4": {
"unit": "game/mainProgram0800",
"line": "minus the sprite origin"
},
"3AD7": {
"unit": "game/mainProgram0800",
"line": "start column inside the sprite (0-23)"
},
"3AD9": {
"unit": "game/mainProgram0800",
"line": "line end column"
},
"3ADB": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"3ADC": {
"unit": "game/mainProgram0800",
"line": "minus the sprite origin"
},
"3ADF": {
"unit": "game/mainProgram0800",
"line": "end column inside the sprite"
},
"3AE1": {
"unit": "game/mainProgram0800",
"line": "line start row"
},
"3AE3": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"3AE4": {
"unit": "game/mainProgram0800",
"line": "minus the sprite origin"
},
"3AE7": {
"unit": "game/mainProgram0800",
"line": "start row inside the sprite (0-20)"
},
"3AE9": {
"unit": "game/mainProgram0800",
"line": "line end row"
},
"3AEB": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"3AEC": {
"unit": "game/mainProgram0800",
"line": "minus the sprite origin"
},
"3AEF": {
"unit": "game/mainProgram0800",
"line": "end row inside the sprite"
},
"3AF1": {
"unit": "game/mainProgram0800",
"line": "sprite X position"
},
"3AF4": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"3AF5": {
"unit": "game/mainProgram0800",
"line": "+2 pixels: a map cell is 4x4 pixels, so this centres the line inside it"
},
"3AF7": {
"unit": "game/mainProgram0800",
"line": "store the sprite X position"
},
"3AFA": {
"unit": "game/mainProgram0800",
"line": "sprite Y position"
},
"3AFD": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"3AFE": {
"unit": "game/mainProgram0800",
"line": "same +2 vertically"
},
"3B00": {
"unit": "game/mainProgram0800",
"line": "store the sprite Y position"
},
"3B03": {
"unit": "game/mainProgram0800",
"block": [
"Plot the segment into the sprite shape and show it for 8 frames."
],
"line": "colour 1 = white"
},
"3B05": {
"unit": "game/mainProgram0800",
"line": "$900B = shadow of sprite 3's colour register $D02A"
},
"3B08": {
"unit": "game/mainProgram0800",
"line": "attacker type"
},
"3B0B": {
"unit": "game/mainProgram0800",
"line": "BOOMER?"
},
"3B0D": {
"unit": "game/mainProgram0800",
"line": "no -> solid line"
},
"3B0F": {
"unit": "game/mainProgram0800",
"line": "bit 7 = draw the line dotted (a lobbed shell)"
},
"3B11": {
"unit": "game/mainProgram0800",
"line": "plot the segment into sprite 3's shape"
},
"3B14": {
"unit": "game/mainProgram0800",
"line": "small diamond at the impact end"
},
"3B17": {
"unit": "game/mainProgram0800",
"line": "sprite 3"
},
"3B19": {
"unit": "game/mainProgram0800",
"line": "switch it on"
},
"3B1C": {
"unit": "game/mainProgram0800",
"line": "8 frames"
},
"3B1E": {
"unit": "game/mainProgram0800",
"line": "the IRQ hides the sprite again when this runs out"
},
"3B21": {
"unit": "game/mainProgram0800",
"line": "back to resolveAttack"
},
"3B22": {
"unit": "game/mainProgram0800",
"routine": [
"applyHitToUnit - Takes A points of damage off unit Y and deals with everything that follows. For one of the local player's own units it warns when a spy is shot at for the first time (message $0E) and shakes the screen with sound 8 when the player's own comcen is hit. Either comcen halves the damage, caps it at the energy it has left, has one of its systems knocked out at random when it is ours, and scores 2 points per energy point for the attacking side. Then the energy is reduced and the hit recorded; a unit that runs out of energy is marked destroyed, while a comcen instead ends the game through endGame.",
"In: A = damage points, Y = target unit",
"Out: hitDamage $9240 = the damage actually applied; the target's energy $F9C4, movement points $FA28, hit effect $FA8C, display flags $FB54 and type byte $F76C; scores $92A2-$92A5 and $9208; screenShakeTimer $92C8; gameEndReason $0BA8; sounds, messages and a redraw of the cell",
"Called from: resolveAttack ($39FD), cmdMissileStrike ($5413), the drone blast in overlay B ($7312); the alternate entry applyHitToUnitNoAlert ($3B52) from destroyUnit ($5465)"
],
"block": [
"Reactions that only happen for the local player's own units. The branch opcode at $3B2C is",
"patched per side by overlay A ($7BEC), so 'below unit 50' means 'mine' for side 0 and 'the",
"opponent's' for side 1."
],
"line": "$9240 = the damage points to apply"
},
"3B25": {
"unit": "game/mainProgram0800",
"line": "target's type byte"
},
"3B28": {
"unit": "game/mainProgram0800",
"line": "0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN"
},
"3B2A": {
"unit": "game/mainProgram0800",
"line": "unit 50 = the first unit of side 1"
},
"3B2C": {
"unit": "game/mainProgram0800",
"line": "opcode patched per side ($7BEC): the opponent's units skip the alerts below"
},
"3B2E": {
"unit": "game/mainProgram0800",
"line": "one of ours - is it a SPY?"
},
"3B30": {
"unit": "game/mainProgram0800",
"line": "no"
},
"3B32": {
"unit": "game/mainProgram0800",
"line": "its energy byte"
},
"3B35": {
"unit": "game/mainProgram0800",
"line": "bits 0-5 = energy 0-50"
},
"3B37": {
"unit": "game/mainProgram0800",
"line": "still untouched at 50?"
},
"3B39": {
"unit": "game/mainProgram0800",
"line": "already damaged -> warn only on the very first hit"
},
"3B3B": {
"unit": "game/mainProgram0800",
"line": "message $0E 'SPY UNDER ATTACK!'"
},
"3B3D": {
"unit": "game/mainProgram0800",
"line": "queue it (dropped during solo/film playback)"
},
"3B40": {
"unit": "game/mainProgram0800",
"line": "restore the type value the message call clobbered"
},
"3B42": {
"unit": "game/mainProgram0800",
"line": "is this our own COMCEN being hit?"
},
"3B44": {
"unit": "game/mainProgram0800",
"line": "no -> straight on"
},
"3B46": {
"unit": "game/mainProgram0800",
"line": "8 frames"
},
"3B48": {
"unit": "game/mainProgram0800",
"line": "$92C8: shake the screen while the comcen is being hit"
},
"3B4B": {
"unit": "game/mainProgram0800",
"line": "sound 8 = own comcen hit"
},
"3B4E": {
"unit": "game/mainProgram0800",
"line": "restore the type value"
},
"3B50": {
"unit": "game/mainProgram0800",
"line": "(always taken)"
},
"3B52": {
"unit": "game/mainProgram0800",
"routine": [
"applyHitToUnitNoAlert - Alternate entry of applyHitToUnit that skips the block above, i.e. the spy warning, the screen shake and the comcen hit sound that only fire for the local player's own units. Everything else - the comcen half-damage and scoring, the energy loss and the destruction handling - is identical.",
"In: A = damage points, Y = target unit",
"Out: as applyHitToUnit",
"Called from: destroyUnit ($5465) with A = $34, i.e. more than any unit's 50 energy, so the SELF DESTRUCT command always kills"
],
"line": "alternate entry used by SELF DESTRUCT ($5465): damage without the own-unit alerts"
},
"3B55": {
"unit": "game/mainProgram0800",
"line": "target's type byte"
},
"3B58": {
"unit": "game/mainProgram0800",
"line": "type 0-4"
},
"3B5A": {
"unit": "game/mainProgram0800",
"block": [
"A hit on either comcen: half damage, capped at what is left, and the only thing in the game",
"that scores points. The score table is indexed by the damaged side - slot X holds the points",
"scored against side X, which is the other side's running total (see $2965)."
],
"line": "type 4 = COMCEN"
},
"3B5C": {
"unit": "game/mainProgram0800",
"line": "an ordinary unit -> straight to the damage"
},
"3B5E": {
"unit": "game/mainProgram0800",
"line": "$9236 = the local player's own comcen (unit 49 or 99)"
},
"3B61": {
"unit": "game/mainProgram0800",
"line": "the opponent's comcen -> no system damage rolled here"
},
"3B63": {
"unit": "game/mainProgram0800",
"line": "knock out one of our comcen's systems at random"
},
"3B66": {
"unit": "game/mainProgram0800",
"line": "the call clobbers Y"
},
"3B69": {
"unit": "game/mainProgram0800",
"line": "a comcen only ever takes half damage"
},
"3B6C": {
"unit": "game/mainProgram0800",
"line": "its energy byte"
},
"3B6F": {
"unit": "game/mainProgram0800",
"line": "energy it has left"
},
"3B71": {
"unit": "game/mainProgram0800",
"line": "more damage than energy?"
},
"3B74": {
"unit": "game/mainProgram0800",
"line": "no"
},
"3B76": {
"unit": "game/mainProgram0800",
"line": "never score more than the comcen still has"
},
"3B79": {
"unit": "game/mainProgram0800",
"line": "X = the side that was hit"
},
"3B7B": {
"unit": "game/mainProgram0800",
"line": "unit 50 = first unit of side 1"
},
"3B7D": {
"unit": "game/mainProgram0800",
"line": "side 0"
},
"3B7F": {
"unit": "game/mainProgram0800",
"line": "units 50-99 belong to side 1"
},
"3B80": {
"unit": "game/mainProgram0800",
"line": "make the clock/score line redraw itself ($C519 compares this with the game clock)"
},
"3B83": {
"unit": "game/mainProgram0800",
"line": "the damage actually applied"
},
"3B86": {
"unit": "game/mainProgram0800",
"line": "2 points per energy point taken off a comcen"
},
"3B87": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"3B88": {
"unit": "game/mainProgram0800",
"line": "slot X = points scored against side X = the attacking side's total"
},
"3B8B": {
"unit": "game/mainProgram0800",
"line": "16-bit score, low byte"
},
"3B8E": {
"unit": "game/mainProgram0800",
"line": "no carry"
},
"3B90": {
"unit": "game/mainProgram0800",
"line": "carry into the high byte of the 16-bit score"
},
"3B93": {
"unit": "game/mainProgram0800",
"block": [
"Take the energy off and record the hit."
],
"line": "(reloading A is redundant - reduceUnitEnergy reads $9240 itself)"
},
"3B96": {
"unit": "game/mainProgram0800",
"line": "energy = energy - damage, with a floor of 0"
},
"3B99": {
"unit": "game/mainProgram0800",
"line": "the damage again"
},
"3B9C": {
"unit": "game/mainProgram0800",
"line": "flash the unit, pin it down and flag a status refresh"
},
"3B9F": {
"unit": "game/mainProgram0800",
"line": "its energy byte"
},
"3BA2": {
"unit": "game/mainProgram0800",
"line": "any energy left?"
},
"3BA4": {
"unit": "game/mainProgram0800",
"line": "still alive -> just redraw it"
},
"3BA6": {
"unit": "game/mainProgram0800",
"block": [
"The unit ran out of energy. An ordinary unit is marked destroyed and gets the death flash;",
"a comcen never carries the dead bit - losing it ends the game instead."
],
"line": "type byte"
},
"3BA9": {
"unit": "game/mainProgram0800",
"line": "bit 7 = destroyed"
},
"3BAB": {
"unit": "game/mainProgram0800",
"line": "the unit is dead"
},
"3BAE": {
"unit": "game/mainProgram0800",
"line": "type 0-4"
},
"3BB0": {
"unit": "game/mainProgram0800",
"line": "was it a COMCEN?"
},
"3BB2": {
"unit": "game/mainProgram0800",
"line": "an ordinary unit -> death flash"
},
"3BB4": {
"unit": "game/mainProgram0800",
"line": "type byte again"
},
"3BB7": {
"unit": "game/mainProgram0800",
"line": "a comcen never carries the dead bit ..."
},
"3BB9": {
"unit": "game/mainProgram0800",
"line": "... it stays on the map"
},
"3BBC": {
"unit": "game/mainProgram0800",
"line": "flags of the destroyed comcen"
},
"3BBF": {
"unit": "game/mainProgram0800",
"line": "bit 5 = its side"
},
"3BC1": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"3BC2": {
"unit": "game/mainProgram0800",
"line": "$20 = side 0 knocked out, $40 = side 1 knocked out"
},
"3BC4": {
"unit": "game/mainProgram0800",
"line": "bit 7 = the game ended by a knockout"
},
"3BC6": {
"unit": "game/mainProgram0800",
"line": "keep any reason that was already set"
},
"3BC9": {
"unit": "game/mainProgram0800",
"line": "$0BA8 = the game end reason"
},
"3BCC": {
"unit": "game/mainProgram0800",
"line": "assume the opponent's comcen fell"
},
"3BCE": {
"unit": "game/mainProgram0800",
"line": "unit 50 = first unit of side 1"
},
"3BD0": {
"unit": "game/mainProgram0800",
"line": "opcode patched per side ($7BEF)"
},
"3BD2": {
"unit": "game/mainProgram0800",
"line": "X = 0 when it was our own comcen that fell"
},
"3BD3": {
"unit": "game/mainProgram0800",
"line": "halt everything, reveal the units and show the result"
},
"3BD6": {
"unit": "game/mainProgram0800",
"line": "$80 = a hit effect is pending, 5 = the 'destroyed' flash"
},
"3BD8": {
"unit": "game/mainProgram0800",
"line": "$FA8C = hit effect of the unit"
},
"3BDB": {
"unit": "game/mainProgram0800",
"line": "redraw the unit's cell on both map panels"
},
"3BDE": {
"unit": "game/mainProgram0800",
"line": "back to the caller"
},
"3BDF": {
"unit": "game/mainProgram0800",
"routine": [
"reduceUnitEnergyBy - Stores A as the damage in hitDamage $9240 and falls into reduceUnitEnergy.",
"In: A = energy points to take off, Y = unit",
"Out: see reduceUnitEnergy",
"Called from: resolveAttack ($3947, the blitz charge the attacker pays for) and $3EF8 (1 point per blitz step)"
],
"line": "$9240 = the energy points to take off"
},
"3BE2": {
"unit": "game/mainProgram0800",
"routine": [
"reduceUnitEnergy - Subtracts hitDamage $9240 from the energy of unit Y, keeping the two visibility flags in bits 6-7 and stopping at 0.",
"In: Y = unit, hitDamage $9240",
"Out: $F9C4[Y] updated; the ORA operand orVisibilityBits+1 is patched with the saved flags",
"Called from: applyHitToUnit ($3B96); reduceUnitEnergyBy falls into it"
],
"line": "energy byte: bits 0-5 = 0-50, bits 6-7 = the spotted/visible flags"
},
"3BE5": {
"unit": "game/mainProgram0800",
"line": "keep the whole byte for a moment"
},
"3BE6": {
"unit": "game/mainProgram0800",
"line": "isolate the two visibility flags"
},
"3BE8": {
"unit": "game/mainProgram0800",
"line": "patch them into the ORA below"
},
"3BEB": {
"unit": "game/mainProgram0800",
"line": "the byte back off the stack"
},
"3BEC": {
"unit": "game/mainProgram0800",
"line": "energy value only"
},
"3BEE": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"3BEF": {
"unit": "game/mainProgram0800",
"line": "minus the damage"
},
"3BF2": {
"unit": "game/mainProgram0800",
"line": "no borrow -> the result is still positive"
},
"3BF4": {
"unit": "game/mainProgram0800",
"line": "energy never goes below 0"
},
"3BF6": {
"unit": "game/mainProgram0800",
"line": "operand = the two visibility bits saved at $3BE6"
},
"3BF8": {
"unit": "game/mainProgram0800",
"line": "energy and visibility flags back into the array"
},
"3BFB": {
"unit": "game/mainProgram0800",
"line": "back to the caller"
},
"3BFC": {
"unit": "game/mainProgram0800",
"routine": [
"recordUnitHit - Records the visible effect of a hit on unit Y: the damage, capped at 20, is stored as $80|damage in the hit-effect byte $FA8C that drives the flash and the PINNED status word, the same number of movement points is taken off the signed counter $FA28 (clamped at -127), and bit 1 of the display flags asks for the console entry to be refreshed.",
"In: A = damage points, Y = unit",
"Out: $FA8C[Y] = $80|damage, $FA28[Y] reduced, $FB54[Y] bit 1 set; the SBC operand subtractPinnedDamage+1 is patched",
"Called from: applyHitToUnit ($3B9C)"
],
"line": "more than 20 points?"
},
"3BFE": {
"unit": "game/mainProgram0800",
"line": "no"
},
"3C00": {
"unit": "game/mainProgram0800",
"line": "the effect saturates at 20 points"
},
"3C02": {
"unit": "game/mainProgram0800",
"line": "patch the subtraction below"
},
"3C05": {
"unit": "game/mainProgram0800",
"line": "bit 7 = a hit effect is pending on this unit"
},
"3C07": {
"unit": "game/mainProgram0800",
"line": "$FA8C drives the hit flash and the PINNED status word"
},
"3C0A": {
"unit": "game/mainProgram0800",
"line": "$FA28 = signed movement points"
},
"3C0D": {
"unit": "game/mainProgram0800",
"line": "prepare the subtraction"
},
"3C0E": {
"unit": "game/mainProgram0800",
"line": "operand = the damage: being shot at pins the unit down"
},
"3C10": {
"unit": "game/mainProgram0800",
"line": "no signed overflow"
},
"3C12": {
"unit": "game/mainProgram0800",
"line": "clamp at -127"
},
"3C14": {
"unit": "game/mainProgram0800",
"line": "movement points back into the array"
},
"3C17": {
"unit": "game/mainProgram0800",
"line": "display flags"
},
"3C1A": {
"unit": "game/mainProgram0800",
"line": "bit 1 = the unit's console entry needs refreshing"
},
"3C1C": {
"unit": "game/mainProgram0800",
"line": "display flags back into the array"
},
"3C1F": {
"unit": "game/mainProgram0800",
"line": "back to applyHitToUnit"
},
"3C20": {
"unit": "game/mainProgram0800",
"routine": [
"orderAttackUnitAtCursor - Fire-button action of the attack cursor. It reads the map cell under the battlefield cursor (always the centre of the 7x5 view, viewOriginCol+3 / viewOriginRow+2); if an enemy unit stands there it queues command $87 SET TARGET carrying the selected unit and that target, otherwise it just beeps.",
"In: zp_A5/zp_A6 = view origin, selectedUnit $90F8, the map, unit flags $F708",
"Out: cmdParam1/cmdParam2 (zp_74/zp_75) and a 3-byte command $87 in the outgoing ring, or sound 3",
"Called from: the target cursor loop ($21F2, with cursorTargetMode zp_BB > 0)"
],
"line": "left edge of the 7x5 battlefield view"
},
"3C22": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"3C23": {
"unit": "game/mainProgram0800",
"line": "the cursor sits 3 cells in ..."
},
"3C25": {
"unit": "game/mainProgram0800",
"line": "-> X = cursor column"
},
"3C26": {
"unit": "game/mainProgram0800",
"line": "top edge of the view"
},
"3C28": {
"unit": "game/mainProgram0800",
"line": "prepare the addition"
},
"3C29": {
"unit": "game/mainProgram0800",
"line": "... and 2 cells down: the centre cell"
},
"3C2B": {
"unit": "game/mainProgram0800",
"line": "-> Y = cursor row"
},
"3C2C": {
"unit": "game/mainProgram0800",
"line": "read the cell under the cursor"
},
"3C2F": {
"unit": "game/mainProgram0800",
"line": "bare terrain -> nothing to shoot at"
},
"3C31": {
"unit": "game/mainProgram0800",
"line": "Y = $80|index, which the base-$80 alias tables undo"
},
"3C32": {
"unit": "game/mainProgram0800",
"line": "the unit the order is for"
},
"3C35": {
"unit": "game/mainProgram0800",
"line": "flags of the unit under the cursor ($F708-$80)"
},
"3C38": {
"unit": "game/mainProgram0800",
"line": "compare them with the selected unit's flags"
},
"3C3B": {
"unit": "game/mainProgram0800",
"line": "bit 5 = side"
},
"3C3D": {
"unit": "game/mainProgram0800",
"line": "same side -> one of ours, not a target"
},
"3C3F": {
"unit": "game/mainProgram0800",
"line": "argument 1 = the unit that will fire"
},
"3C41": {
"unit": "game/mainProgram0800",
"line": "the target's cell byte"
},
"3C42": {
"unit": "game/mainProgram0800",
"line": "strip the map's 'unit here' marker bit"
},
"3C44": {
"unit": "game/mainProgram0800",
"line": "argument 2 = the target unit"
},
"3C46": {
"unit": "game/mainProgram0800",
"line": "command $87 SET TARGET"
},
"3C48": {
"unit": "game/mainProgram0800",
"line": "queue the code plus its two arguments for both machines"
},
"3C4B": {
"unit": "game/mainProgram0800",
"line": "sound 3 = error beep"
},
"3C4D": {
"unit": "game/mainProgram0800",
"line": "nothing was ordered"
},
"3C50": {
"unit": "game/mainProgram0800",
"routine": [
"setBoomerTargetCmd - Execution half of command $87 SET TARGET. The boomer's ordinal among all boomers is the index into boomerTargetTable, where the target unit is stored with bit 7 set to mark it as a lock the player asked for. The listing still shows these 13 bytes as data; they are entered by the JMP $3C50 at $5182 inside the command dispatcher.",
"In: cmdParam0 zp_73 = boomer unit index, cmdParam2 zp_75 = target unit index",
"Out: boomerTargetTable $91DC[ordinal] = $80|target; Y = the ordinal, X = the boomer index",
"Called from: cmdSetTarget ($5182), the handler of command $87 on both machines"
],
"line": "boomer unit index"
},
"3C52": {
"unit": "game/mainProgram0800",
"line": "Y = its ordinal among all boomers = its slot in $91DC"
},
"3C55": {
"unit": "game/mainProgram0800",
"line": "target unit index"
},
"3C57": {
"unit": "game/mainProgram0800",
"line": "bit 7 = the player locked this target on purpose"
},
"3C59": {
"unit": "game/mainProgram0800",
"line": "one entry per boomer"
},
"3C5C": {
"unit": "game/mainProgram0800",
"line": "back to the command dispatcher"
},
"3C5D": {
"unit": "game/mainProgram0800",
"routine": [
"countBoomersBelow - Counts the BOOMERs (type 2) with an index below X, which is the ordinal of boomer X among all boomers and therefore its slot in boomerTargetTable. The limit is patched into the CPX at $3C6F.",
"In: X = unit index (exclusive upper limit)",
"Out: Y = the number of boomers below X, X = the input value, C = 1; the operand countBoomersLimit+1 is patched",
"Called from: setBoomerTargetCmd ($3C52), getBoomerTarget ($3C7E)"
],
"line": "patch the loop limit with the unit we are looking for"
},
"3C60": {
"unit": "game/mainProgram0800",
"line": "boomers counted so far"
},
"3C62": {
"unit": "game/mainProgram0800",
"line": "start at unit 0"
},
"3C64": {
"unit": "game/mainProgram0800",
"line": "type byte of this unit"
},
"3C67": {
"unit": "game/mainProgram0800",
"line": "strip the dead/dug-in bits"
},
"3C69": {
"unit": "game/mainProgram0800",
"line": "type 2 = BOOMER"
},
"3C6B": {
"unit": "game/mainProgram0800",
"line": "not a boomer"
},
"3C6D": {
"unit": "game/mainProgram0800",
"line": "count it"
},
"3C6E": {
"unit": "game/mainProgram0800",
"line": "next unit"
},
"3C6F": {
"unit": "game/mainProgram0800",
"line": "operand = the unit index passed in"
},
"3C71": {
"unit": "game/mainProgram0800",
"line": "count every boomer below it"
},
"3C73": {
"unit": "game/mainProgram0800",
"line": "Y = the boomer's slot in boomerTargetTable"
},
"3C74": {
"unit": "game/mainProgram0800",
"routine": [
"getBoomerTarget - Reads the target lock of unit X. For a BOOMER it returns C = 1, Y = the boomer's slot and A = its boomerTargetTable entry (bit 7 = locked by the player, bits 0-6 = the target unit); any other unit type returns C = 0 and a non-zero, positive A. Callers test C and N: the unit options menu offers CLEAR TARGET or SET TARGET, and the update loop drops the lock when the boomer can no longer fire.",
"In: X = unit index",
"Out: for a boomer C = 1, A = $91DC[ordinal], Y = the ordinal; otherwise C = 0 and A = type EOR 2",
"Called from: $2432, selectBoomerTarget ($3C8F), the per-unit update loop ($444D), cmdClearTarget ($5162), the unit options menu ($5EFF)"
],
"line": "C = 0 will mean 'not a boomer'"
},
"3C75": {
"unit": "game/mainProgram0800",
"line": "type byte"
},
"3C78": {
"unit": "game/mainProgram0800",
"line": "strip the dead/dug-in bits"
},
"3C7A": {
"unit": "game/mainProgram0800",
"line": "zero only for type 2 = BOOMER"
},
"3C7C": {
"unit": "game/mainProgram0800",
"line": "any other type: return C=0 and A = type EOR 2"
},
"3C7E": {
"unit": "game/mainProgram0800",
"line": "Y = the boomer's slot in $91DC (this also returns C=1)"
},
"3C81": {
"unit": "game/mainProgram0800",
"line": "bit 7 = locked, bits 0-6 = the target unit"
},
"3C84": {
"unit": "game/mainProgram0800",
"line": "back to the caller"
},
"3C85": {
"unit": "game/mainProgram0800",
"routine": [
"selectBoomerTarget - Picks what boomer Y shoots at this round. It first runs the weapon-range scan (scanVisibleEnemies with X = $80) to build the candidate lists, then walks them: if the target the player locked on is still listed it is kept and nothing else is looked at; otherwise an enemy COMCEN wins outright and, failing that, the nearest candidate is taken - and the now stale lock in boomerTargetTable is cleared.",
"In: Y = boomer unit index; boomerTargetTable $91DC; the candidate lists $0400/$0432/$0464 and zp_2F produced by the scan",
"Out: fireTargetUnit $91F0 = the chosen target ($FF = none), fireTargetDistance $91F1 = its squared distance, targetDirection zp_6B = the direction 0-3 to turn to; $91DC[ordinal] cleared when the lock was lost; zp_18/zp_19/zp_1A scratch; operand loadBoomerUnit+1 patched",
"Called from: the per-unit update loop ($4425) for units of type 2"
],
"line": "remember the boomer: the scan below needs X and Y for itself"
},
"3C88": {
"unit": "game/mainProgram0800",
"line": "bit 7 = weapon-range scan, not a spotting scan"
},
"3C8A": {
"unit": "game/mainProgram0800",
"line": "build the candidate lists $0400/$0432/$0464"
},
"3C8D": {
"unit": "game/mainProgram0800",
"line": "operand = the boomer unit index"
},
"3C8F": {
"unit": "game/mainProgram0800",
"line": "A = its lock byte, Y = its slot in $91DC"
},
"3C92": {
"unit": "game/mainProgram0800",
"line": "keep the slot for the 'lost the target' case below"
},
"3C94": {
"unit": "game/mainProgram0800",
"line": "strip the lock bit -> a plain unit index ($80 when nothing is locked)"
},
"3C96": {
"unit": "game/mainProgram0800",
"line": "the unit this boomer is currently locked on"
},
"3C98": {
"unit": "game/mainProgram0800",
"line": "$FF ..."
},
"3C9A": {
"unit": "game/mainProgram0800",
"line": "... no best distance yet"
},
"3C9D": {
"unit": "game/mainProgram0800",
"line": "$91F0 = no target chosen yet"
},
"3CA0": {
"unit": "game/mainProgram0800",
"line": "index of the last candidate"
},
"3CA2": {
"unit": "game/mainProgram0800",
"line": "the loop walks the list backwards"
},
"3CA4": {
"unit": "game/mainProgram0800",
"line": "nothing within weapon range at all"
},
"3CA6": {
"unit": "game/mainProgram0800",
"block": [
"Walk the candidate list backwards looking for the best target."
],
"line": "current candidate"
},
"3CA8": {
"unit": "game/mainProgram0800",
"line": "its unit index, or $FF where scanVisibleEnemies rejected the entry"
},
"3CAB": {
"unit": "game/mainProgram0800",
"line": "rejected entry -> skip it"
},
"3CAD": {
"unit": "game/mainProgram0800",
"line": "is this the target the player locked on?"
},
"3CAF": {
"unit": "game/mainProgram0800",
"line": "yes -> keep firing at it and stop looking"
},
"3CB1": {
"unit": "game/mainProgram0800",
"line": "unit 49 = side 0's comcen"
},
"3CB3": {
"unit": "game/mainProgram0800",
"line": "yes"
},
"3CB5": {
"unit": "game/mainProgram0800",
"line": "unit 99 = side 1's comcen"
},
"3CB7": {
"unit": "game/mainProgram0800",
"line": "an ordinary unit -> compare distances"
},
"3CB9": {
"unit": "game/mainProgram0800",
"line": "a comcen outranks everything ..."
},
"3CBB": {
"unit": "game/mainProgram0800",
"line": "... and the DEC below ends the search right after this entry"
},
"3CBD": {
"unit": "game/mainProgram0800",
"line": "(always taken; note that the comcen's distance is never stored)"
},
"3CBF": {
"unit": "game/mainProgram0800",
"line": "squared distance of this candidate"
},
"3CC2": {
"unit": "game/mainProgram0800",
"line": "against the best so far"
},
"3CC5": {
"unit": "game/mainProgram0800",
"line": "farther away -> keep the old one"
},
"3CC7": {
"unit": "game/mainProgram0800",
"line": "a new nearest candidate"
},
"3CCA": {
"unit": "game/mainProgram0800",
"line": "its unit index"
},
"3CCD": {
"unit": "game/mainProgram0800",
"line": "$91F0 = the unit that will be fired at"
},
"3CD0": {
"unit": "game/mainProgram0800",
"line": "direction 0-3 toward it"
},
"3CD3": {
"unit": "game/mainProgram0800",
"line": "zp_6B, copied into the unit's facing bits at $4464"
},
"3CD5": {
"unit": "game/mainProgram0800",
"line": "next candidate (the list is walked backwards)"
},
"3CD7": {
"unit": "game/mainProgram0800",
"line": "until the whole list has been looked at"
},
"3CD9": {
"unit": "game/mainProgram0800",
"block": [
"Reached whenever the locked target was not in the list any more (and also when there never was",
"one): the lock is dropped, so the unit options menu offers SET TARGET again."
],
"line": "the boomer's slot in $91DC"
},
"3CDB": {
"unit": "game/mainProgram0800",
"line": "0 = no target locked"
},
"3CDD": {
"unit": "game/mainProgram0800",
"line": "clear the stale lock"
},
"3CE0": {
"unit": "game/mainProgram0800",
"line": "(always taken)"
},
"3CE2": {
"unit": "game/mainProgram0800",
"block": [
"The locked target is still within range: keep it and ignore everything else in the list."
],
"line": "Y still holds the locked unit"
},
"3CE5": {
"unit": "game/mainProgram0800",
"line": "its squared distance"
},
"3CE8": {
"unit": "game/mainProgram0800",
"line": "$91F1 = distance to the locked target"
},
"3CEB": {
"unit": "game/mainProgram0800",
"line": "direction 0-3 toward it"
},
"3CEE": {
"unit": "game/mainProgram0800",
"line": "zp_6B = direction to turn to"
},
"3CF0": {
"unit": "game/mainProgram0800",
"line": "back to the per-unit update loop"
}
},
"dataTypes": {
"379C": [
"byte",
2
],
"3860": [
"byte",
16
],
"38A4": [
"byte",
15
],
"38B3": [
"byte",
8
],
"38BB": [
"byte",
7
]
},
"zpComments": {
"1F": "general scratch: line-of-sight ridge tracker ($80 = the line has not risen yet, otherwise the last rise in terrain height, so a drop after it means a ridge hides the target); divide bit counter; group destination centre row"
}
}