modemwars/survey/game_ovl_6F00_B_868C.json
2026-08-23 02:09:40 -05:00

369 lines
22 KiB
JSON
Generated

{
"chunk": "game_ovl_6F00_B_868C",
"unit": "game/ovl_6F00_B",
"routines": [],
"variables": [
{
"addr": "87FF",
"scope": "abs",
"name": "overlayVariantFlag",
"meaning": "Overlay identity byte, the last byte of the $6F00-$87FF overlay: $FF in variant B, $00 in variant A (verified against extracted/runtimeImage_A.bin / _B.bin). Read at $1036 (bmi -> loadOverlayA, i.e. ensureOverlayA) and $1080 (bpl -> loadOverlayB, i.e. ensureOverlayB) so the main program can tell which variant is resident and skip a redundant disk load.",
"confidence": "high"
},
{
"addr": "8726",
"scope": "abs",
"name": "showMessageViewWord",
"meaning": "The four '*' bytes inside the msgShowViewTemplate string ($8721 'SHOW ****'). The film-view key handler copies filmViewWordTable[(playModeFlags & 3) * 4] here 4 bytes at a time ($4120 lda filmViewWordTable,y / $4123 sta $8726,x) before queueing message id 6.",
"confidence": "high"
},
{
"addr": "8730",
"scope": "abs",
"name": "speedMessageDigit",
"meaning": "The single '*' byte of msgSpeedTemplate ($872A 'SPEED *'). $40DE stores the raw key code of the pressed digit ($B1-$B9) here; because bit 7 is set the digit is both the character and the string terminator. Message id 7 is queued immediately after at $40E3.",
"confidence": "high"
},
{
"addr": "0500",
"scope": "abs",
"name": "messagePtrLoTable",
"meaning": "Low bytes of the 36-entry message string address table, indexed by message id $00-$23. Ids 2,5,6,7,8,$0C,$0D,$0E,$0F,$11,$12,$13,$14,$15,$16 point into this chunk. Entries $1E,$20,$21 are $FF/$FF placeholders filled at run time by sub_C924 (X = id, A = lo, Y = hi).",
"confidence": "high"
},
{
"addr": "0524",
"scope": "abs",
"name": "messagePtrHiTable",
"meaning": "High bytes of messagePtrLoTable, same 36 entries. The message display code fetches $0500,x / $0524,x and prints the string through printString ($C133).",
"confidence": "high"
},
{
"addr": "0612",
"scope": "abs",
"name": "messageSoundTable",
"meaning": "36-entry sound-effect number per message id, $80 = silent. For this chunk's ids: 2 -> 3, 5 -> 4, 6 -> 3, 7 -> 3, 8 -> 4, $0C-$0F -> 4, $11 -> $0E, $12 -> $0D, $13 -> $80 (silent), $14 -> $0E, $15 -> $0D, $16 -> $19.",
"confidence": "medium"
},
{
"addr": "9249",
"scope": "abs",
"name": "messageQueue",
"meaning": "8-entry priority-sorted ring of pending message ids managed by sub_C566, with head at $9251 and tail at $9252. Higher id sorts ahead (cmp $9249,y / bcs).",
"confidence": "medium"
},
{
"addr": "92A1",
"scope": "abs",
"name": "persistentMessageId",
"meaning": "Message id the status line falls back to when the queue drains; $9E = none. computeGameResult ($28F1) stores the chosen result id ($11-$15) here, queues it, then resets it to $9E.",
"confidence": "medium"
},
{
"addr": "0B9B",
"scope": "abs",
"name": "playModeFlags",
"meaning": "Bit 7 = solo trainer / film playback (sub_C560 silently drops a message when it is set, showDroneAlert $8214 does the same). Bits 0-1 = film view mode, 0 US / 1 THEM / 2 BOTH, cycled $80 -> $81 -> $82 -> $80 by the CLR/HOME key at $4108-$4116.",
"confidence": "high"
},
{
"addr": "9248",
"scope": "abs",
"name": "lastKeyPressed",
"meaning": "Last typed key code with bit 7 set ($B1-$B9 = digits 1-9, $8C = CLR/HOME, $8E seen at $C699), $FF = none. Consumed and reset to $FF by the film replay key handler at $40C5-$40E8 and $40F7-$4100.",
"confidence": "high"
},
{
"addr": "92E3",
"scope": "abs",
"name": "stepIntervalFrames",
"meaning": "Frames between engine steps. The film replay speed keys set it to 8 - (key - $B1), i.e. 9 - digit for digits 1-9 (fastest = 0 for '9'), then display 'SPEED n'.",
"confidence": "high"
},
{
"addr": "0B9F",
"scope": "abs",
"name": "localPlayerIndex",
"meaning": "0 or 1: the side played on this machine. computeGameResult compares it with the index of the side that lost ($2965, $298F) to choose between the WE LOST / WE WON pair, and the score display at $C6CE-$C6DC prints scores[localPlayerIndex ^ 1] (US) before scores[localPlayerIndex] (THEM).",
"confidence": "high"
},
{
"addr": "0BA8",
"scope": "abs",
"name": "gameOverFlags",
"meaning": "End-of-game reason read by computeGameResult ($28FC). Bit 7 set = knockout (comcen destroyed), clear = time ran out and points decide. On the knockout path bits 5-6 select the loser: $60 = both, so a draw; $40 or higher = side 1 lost; below $40 = side 0 lost.",
"confidence": "medium"
},
{
"addr": "92A2",
"scope": "abs",
"name": "damageAgainstSideLo",
"meaning": "Two-entry (index = side 0/1) low byte of the running 16-bit damage total scored AGAINST that side; high byte at $92A4. It is indexed by the VICTIM: $3B79-$3B8B sets X = 0 for units 0-49 and X = 1 for units 50-99 and adds 2 * damage to $92A2,x. So $92A2[i] is effectively the score belonging to side i^1, which is why the display prints $92A2[localPlayerIndex ^ 1] as 'us'.",
"confidence": "high"
},
{
"addr": "92A4",
"scope": "abs",
"name": "damageAgainstSideHi",
"meaning": "High bytes of damageAgainstSideLo (index = side 0/1).",
"confidence": "high"
},
{
"addr": "922A",
"scope": "abs",
"name": "comcenStunnedFlag",
"meaning": "Bit 7 set makes the console status area print msgComcenStunned ($8742) at column 5 ($17C4-$17D4). Bit 6 is tested separately at $40BA on the non-film path of the same input routine.",
"confidence": "medium"
},
{
"addr": "0B7F",
"scope": "abs",
"name": "ownerNameString",
"meaning": "Bit-7-terminated owner/player name printed at $1616-$161A immediately before statsTabLabels on the STATS tab (text window left 10, column 3).",
"confidence": "medium"
},
{
"addr": "90FA",
"scope": "abs",
"name": "currentUnitIndex",
"meaning": "Unit index (0-99) the comm module is servicing; loaded into Y at $EACF in the T35 build, where cpy #$32 splits the two sides. Relevant here only because it makes the 'sta $87CE,y' at $EB29/$EB53 address $8800+ rather than this chunk's strings.",
"confidence": "high"
},
{
"addr": "A2",
"scope": "zp",
"name": "stringPtr",
"meaning": "Pointer (lo at $A2, hi at $A3) to the bit-7-terminated string being drawn; printString ($C133) does sta zp_A2 / sty zp_A3 on entry. Every caption in this chunk is passed as A = low byte, Y = $86 or $87.",
"confidence": "high"
},
{
"addr": "3F",
"scope": "zp",
"name": "textCursorCol",
"meaning": "Text cursor column relative to the text window left edge; sub_C099 forms the absolute column as zp_3F + zp_3B before each caption is printed.",
"confidence": "high"
},
{
"addr": "40",
"scope": "zp",
"name": "textCursorRow",
"meaning": "Text cursor row passed to calcCellPointersXY by sub_C099.",
"confidence": "high"
},
{
"addr": "3B",
"scope": "zp",
"name": "textWindowLeft",
"meaning": "Left column of the text window added to textCursorCol by sub_C099; set to $1F (31) for the two-line DRONE ALERT panel and $0A (10) for the STATS tab labels.",
"confidence": "high"
},
{
"addr": "3D",
"scope": "zp",
"name": "textColour",
"meaning": "Colour byte used while drawing text into the bitmap: $CF for the DRONE caption, $CD for LAUNCH and FIRE, $C7 for STRIKE. sub_C137 conditionally swaps it for zp_42.",
"confidence": "high"
}
],
"dataBlocks": [
{
"addr": "868C",
"length": 5,
"type": "text",
"name": "strDrone",
"description": "'DRONE' (final E has bit 7). Caption of the drones-left box on the drone screen; printed by overlay B code at $7018-$7033 with textColour $CF, column $20 (32), row $07."
},
{
"addr": "8691",
"length": 6,
"type": "text",
"name": "strLaunch",
"description": "'LAUNCH'. Caption under the LAUNCH button icon of the drone screen; printed at $70B6-$70C5, column $20, row $0E (14)."
},
{
"addr": "8697",
"length": 6,
"type": "text",
"name": "strStrike",
"description": "'STRIKE'. Caption under the STRIKE button icon of the drone screen; printed by sub_70D9 at $70D9-$70EF with textColour $C7, column $20, row $12 (18)."
},
{
"addr": "869D",
"length": 4,
"type": "text",
"name": "strFire",
"description": "'FIRE'. Caption of the missile FIRE button on the main game screen; printed at $7B2E-$7B3D, column $1F (31), row $0C (12)."
},
{
"addr": "86A1",
"length": 11,
"type": "text",
"name": "strDroneAlertTwoLine",
"description": "'DRONE' + $0D + 'ALERT' (final T has bit 7): the two-line label of the top-right DRONE ALERT panel. Printed at $7B67-$7B7A with textWindowLeft $1F, column 0, row 3; the surrounding panel colour (droneAlertColour, $02 or $20) is what flashes, not the text."
},
{
"addr": "86AC",
"length": 79,
"type": "text",
"name": "statsTabLabels",
"description": "One bit-7-terminated blob of CR-separated row labels for the console STATS tab: CR 'GRUNTS' CR 'RIDERS' CR 'BOOMERS' CR 'SPIES' CR CR 'DRONES' CR 'MISSILES' CR CR 'COMSPEED' CR 'TER PTS' CR 'RECYCLER' CR 'MAP ID' (the final D is $C4). Printed as a single string by the main program at $161D-$1621 right after ownerNameString, with textWindowLeft 10 and column 3; the matching numbers are drawn separately."
},
{
"addr": "86FB",
"length": 12,
"type": "byteTable",
"name": "filmViewWordTable",
"description": "Three fixed-width 4-byte entries used to patch msgShowViewTemplate: 'U','S'|$80,$00,$00 / 'T','H','E','M'|$80 / 'B','O','T','H'|$80. Indexed by (playModeFlags & 3) * 4 at $4116-$412A, producing the status line SHOW US / SHOW THEM / SHOW BOTH. The two padding zeroes of the first entry are copied but never printed because the terminator precedes them."
},
{
"addr": "8707",
"length": 12,
"type": "text",
"name": "msgEndOfFilm",
"description": "'END OF FILM.' - message id 2 (sound 3), queued with sub_C566 at $59FC after setFilmPtrToEnd when a recorded game film has been replayed to its end."
},
{
"addr": "8713",
"length": 14,
"type": "text",
"name": "msgUnitRecycled",
"description": "'UNIT RECYCLED.' - message id 5 (sound 4), queued with sub_C560 at $3EA3 after initUnitRecord, but only when the recycled unit's owner (zp_1A) equals localPlayerIndex."
},
{
"addr": "8721",
"length": 9,
"type": "text",
"name": "msgShowViewTemplate",
"description": "'SHOW ****' - message id 6 template (sound 3). Bytes $8726-$8729 are overwritten with a filmViewWordTable entry, which supplies the bit-7 terminator, before the message is queued at $412C. As stored on disk the template has no terminator and would run on into msgSpeedTemplate."
},
{
"addr": "872A",
"length": 7,
"type": "text",
"name": "msgSpeedTemplate",
"description": "'SPEED *' - message id 7 template (sound 3). The '*' at $8730 is replaced by the pressed digit's key code $B1-$B9, whose bit 7 doubles as the terminator, at $40DE; the message is then queued at $40E3. Also unterminated as stored."
},
{
"addr": "8731",
"length": 17,
"type": "text",
"name": "msgSpottedRecycler",
"description": "'SPOTTED RECYCLER!' - message id 8 (sound 4), queued with sub_C560 at $3F8C right after drawRecyclerOnMap / drawMapCell, i.e. when the recycler cell first becomes visible."
},
{
"addr": "8742",
"length": 15,
"type": "text",
"name": "msgComcenStunned",
"description": "'COMCEN STUNNED!' - message id $0C (sound 4), queued at $0741-$0743 in the $0400-page game-tick code when $9162 reads $FF. The same string is also printed directly into the console status area at column 5 by $17C4-$17D4 while comcenStunnedFlag ($922A) bit 7 is set."
},
{
"addr": "8751",
"length": 21,
"type": "text",
"name": "msgSpottedEnemyComcen",
"description": "'SPOTTED ENEMY COMCEN!' - message id $0D (sound 4), queued with sub_C560 at $36EF when a newly visible enemy unit's type field ($F76C,y & 7) equals 4 (COMCEN)."
},
{
"addr": "8766",
"length": 17,
"type": "text",
"name": "msgSpyUnderAttack",
"description": "'SPY UNDER ATTACK!' - message id $0E (sound 4), queued with sub_C560 at $3B3B when the unit being hit is type 3 (SPY) and its energy field ($F9C4,y & $3F) is still $32 (50 = full), so it fires once, on the first hit."
},
{
"addr": "8777",
"length": 18,
"type": "text",
"name": "msgSpottedEnemySpy",
"description": "'SPOTTED ENEMY SPY!' - message id $0F (sound 4), queued with sub_C560 at $36E7 when a newly visible enemy unit's type field equals 3 (SPY). Types 0-2 (GRUNT, RIDER, BOOMER) are spotted silently."
},
{
"addr": "8789",
"length": 18,
"type": "text",
"name": "msgWeLostOnPoints",
"description": "'WE LOST ON POINTS!' - message id $11 (sound $0E), selected by computeGameResult at $2963-$296B on the time-out path when the losing side equals localPlayerIndex. Overlaid by code in variant A ($8789-$879E hold 22 bytes of A code ending in jmp $C100), so the string is only intact while variant B is resident."
},
{
"addr": "879B",
"length": 17,
"type": "text",
"name": "msgWeWonOnPoints",
"description": "'WE WON ON POINTS!' - message id $12 (sound $0D), the time-out win. Its first four bytes 'WE W' ($879B-$879E) are also clobbered by variant A code; from $879F on the byte content is identical in both variants."
},
{
"addr": "87AC",
"length": 14,
"type": "text",
"name": "msgItWasADraw",
"description": "'IT WAS A DRAW.' - message id $13, the only result message with no sound ($80). Chosen when the 16-bit score difference is zero at time-out, or when gameOverFlags bits 5-6 equal $60 (both comcens knocked out); both sides are then set to 250 points."
},
{
"addr": "87BA",
"length": 20,
"type": "text",
"name": "msgWeWereKnockedOut",
"description": "'WE WERE KNOCKED OUT!' - message id $14 (sound $0E), selected at $298D-$2995 when the knocked-out side equals localPlayerIndex."
},
{
"addr": "87CE",
"length": 21,
"type": "text",
"name": "msgWeWonByAKnockout",
"description": "'WE WON BY A KNOCKOUT!' - message id $15 (sound $0D), the enemy comcen destroyed. Not written by the T35 comm module despite the XREF: see the misclassified entry for $87CE."
},
{
"addr": "87E3",
"length": 10,
"type": "text",
"name": "msgGameOver",
"description": "'GAME OVER.' - message id $16 (sound $19), queued first by computeGameResult at $2901-$2903 on the time-out path, ahead of the result message."
},
{
"addr": "87ED",
"length": 12,
"type": "text",
"name": "msgDroneAlert",
"description": "'DRONE ALERT!' - has no fixed message id; overlay A installs its address into run-time message slot $20 at $7BA6-$7BAC (ldx #$20 / lda #$ED / ldy #$87 / jsr sub_C924) immediately before jumping to the comm module battle-start entry $EE26. During the battle showDroneAlert ($820F, variant B, called from $53AF) plays sound 7 and queues id $20 unless playModeFlags bit 7 (solo/film) is set."
},
{
"addr": "87F9",
"length": 6,
"type": "unknown",
"name": "overlayTailPadding",
"description": "Six $00 bytes between msgDroneAlert and the variant flag, identical in both variants and referenced by nothing."
},
{
"addr": "87FF",
"length": 1,
"type": "byteTable",
"name": "overlayVariantFlag",
"description": "The overlay identity byte: $FF in variant B, $00 in variant A. Bit 7 is read at $1036 and $1080 to decide whether the other variant still has to be pulled from disk."
}
],
"misclassified": [
{
"addr": "868C",
"length": 275,
"actual": "data",
"evidence": "$868C-$879E carries a dozen stray sub_XXXX / L_XXXX / D_XXXX labels (sub_86AA, sub_86C3, sub_8722, sub_872E, sub_8756, sub_8795, L_868E, L_8697, L_86C5, L_86DF, L_86F3, L_8714, L_8734, L_8740, L_8755, L_8765, L_878A, D_868D, D_869A, D_869B, D_8712) and XREF.txt even gives some of them routine names (setDestroyModeMessage $86AA, showCustomSummaryMessage $86C3, setPersistentMessage $8722, appendOnOffWord $872E, showScenarioBannerAndWaitForButton $8756, printCentredTableEntry $8795) while attributing them to unit game/ovl_6F00_B. Those labels and every call/branch reference feeding them ($7B4B, $7D4B, $856D, $8787, $855C, $857F, $8769, $8779, $8213, $8221, $8226, $86AC, $86BA, $86BF, $8707, $86FD) belong to overlay variant A, which holds the custom-scenario dialog code at these addresses. In variant B the region is pure string data; the .s file correctly emits .byte for all of it and contains no instruction in $868C-$87FF."
},
{
"addr": "87CE",
"length": 1,
"actual": "data",
"evidence": "XREF lists msgWeWonByKnockout $87CE with 'w:EB29,EB53', which reads as the T35 comm module overwriting the result string. It does not: sub_EACF ($EACF) begins 'ldy D_90FA / cpy #$32 / bcs L_EAF1', and both stores sit on the L_EAF1 side of that branch, so Y is always $32-$63 (units 50-99) and 'sta $87CE,y' resolves to $8800-$8831 - a 50-entry per-unit byte table in the $8800 work area, not to this chunk."
}
],
"insights": [
"$868C-$87FF of overlay variant B contains no code whatsoever: 24 bit-7-terminated strings, one 12-byte word table, six padding zeroes and the variant flag. Every routine-looking label in the range is an artefact of variant A being merged into the same XREF namespace (see misclassified).",
"Complete message id map (lo bytes at $0500, hi at $0524, 36 ids $00-$23, sound per id at $0612 with $80 = silent): 0 blank line of 33 spaces $0548, 1 WORKING... $056C, 2 END OF FILM. $8707, 3 GAME ABORTED. $0576, 4 $8858, 5 UNIT RECYCLED. $8713, 6 SHOW **** $8721, 7 SPEED * $872A, 8 SPOTTED RECYCLER! $8731, 9 NOT A GAME FILM! $0583, $0A SETUP UNITS. PRESS F 1 WHEN DONE. $0593, $0B $8867, $0C COMCEN STUNNED! $8742, $0D SPOTTED ENEMY COMCEN! $8751, $0E SPY UNDER ATTACK! $8766, $0F SPOTTED ENEMY SPY! $8777, $10 $887E, $11 WE LOST ON POINTS! $8789, $12 WE WON ON POINTS! $879B, $13 IT WAS A DRAW. $87AC, $14 WE WERE KNOCKED OUT! $87BA, $15 WE WON BY A KNOCKOUT! $87CE, $16 GAME OVER. $87E3, $17 $8897, $18 $88B6, $19 TIMEOUT. RUN/STOP TO RESUME. $05B4, $1A $88D6, $1B $05D0, $1C $05D7, $1D $05E9, $1E run-time slot ($FFFF), $1F $88F0, $20 and $21 run-time slots ($FFFF), $22 $05FA, $23 $0608. sub_C566 queues id A into the 8-entry sorted queue at $9249; sub_C560 is a wrapper that first does 'bit $0B9B / bpl sub_C566 / rts', i.e. it drops the message during solo/film play.",
"CORRECTION to the existing computeGameResult ($28F1) annotation in game/main_0800.s, which says message $11 means 'won on points'. It is the other way round: $11 is 'WE LOST ON POINTS!' and $14 is 'WE WERE KNOCKED OUT!'. The X register the routine compares against localPlayerIndex is the LOSING side, and the code is consistent because the $92A2/$92A4 pair is indexed by the VICTIM: $3B79-$3B8B picks X = 0 for damaged units 0-49 and X = 1 for units 50-99 and adds the damage to $92A2,x, so $92A2[i] accumulates the score of side i^1. Hence a larger $92A2[i] means side i is losing; $2931's bcs leaves X = 0 when $92A2[0] >= $92A2[1]; the winner's score (300 + min(|diff|/2,150), or 500 on a knockout) is written into the loser's slot and the loser's score (200, or 150 on a knockout) into the winner's slot via setOtherPlayerPoints. The score display at $C6CE-$C6DC confirms the indexing by printing $92A2[localPlayerIndex ^ 1] first and $92A2[localPlayerIndex] second.",
"Two of the messages are self-modifying text and belong to the film replay key handler at $40B2. Digits '1'-'9' ($B1-$B9 in lastKeyPressed $9248) set stepIntervalFrames $92E3 = 8 - (key - $B1) and store the raw key code into $8730 so 'SPEED n' terminates itself; CLR/HOME ($8C) increments playModeFlags $0B9B with a wrap from $83 back to $80 and copies filmViewWordTable[(flags & 3) * 4] over $8726-$8729 to make 'SHOW US' / 'SHOW THEM' / 'SHOW BOTH'. Both templates are stored unterminated on disk and are only safe to print after being patched.",
"The two overlay variants share their last 97 bytes exactly: a byte compare of extracted/runtimeImage_A.bin and _B.bin shows $868C-$879E differ (variant A holds custom-scenario dialog code there, the last of it a 22-byte block at $8789-$879E ending with jmp $C100), $879F-$87FE are byte-identical, and $87FF differs ($00 in A, $FF in B). That shared tail is what makes overlay A's trick at $7BA6 work: A installs $87ED ('DRONE ALERT!') into run-time message slot $20 while A is resident, and the pointer stays valid after the swap to B because the string lives in the identical tail. Messages $11 and the first four bytes of $12 are corrupted while A is resident, which is harmless since results are only shown during the battle, with B resident.",
"Everything drawn from this chunk goes through the same two-step call: sub_C099 positions the cursor (absolute column = zp_3F + zp_3B, row = zp_40, colour in zp_3D) and printString $C133 takes A = string low byte, Y = high byte into zp_A2/zp_A3. The five UI captions are at fixed spots: DRONE col 32 row 7 colour $CF ($7018), LAUNCH col 32 row 14 ($70B6), STRIKE col 32 row 18 colour $C7 ($70D9), FIRE col 31 row 12 ($7B2E), and the two-line DRONE/ALERT panel at window-left 31, col 0, row 3 ($7B67).",
"The whole STATS tab label column is one 79-byte string with embedded CR ($0D) bytes ($86AC), printed by a single printString call at $161D from the main program - not one call per row. Together with the JSR $8518 / $8632 calls the main program makes into this overlay, that means the console STATS screen implicitly requires overlay variant B to be resident; variant A has code at $86AC.",
"Unit spotting messages come from one spot in the visibility code, $36D7-$36F1: a newly visible enemy unit whose type field ($F76C,y & 7) is 3 queues $0F (SPOTTED ENEMY SPY!) and type 4 queues $0D (SPOTTED ENEMY COMCEN!); types 0-2 say nothing. SPY UNDER ATTACK! ($0E) is separate, raised at $3B3B only when the spy still has full energy ($F9C4,y & $3F == $32), so it announces the first hit rather than every hit."
]
}