{ "chunk": "game_messages8800_8800", "unit": "game/messages8800", "unitLabels": { "game/messages8800": { "8800": "msgGetOpponentOnPhoneAnd", "881C": "msgPressAOrOAndSetModem", "8837": "msgPressSpaceWhenReady", "884E": "msgTrainerName", "8858": "msgOpponentReady", "8867": "msgWaitingForOpponent", "887E": "msgWaitingForConnection", "8897": "msgPickUpPhoneThenPressSpace", "88B6": "msgPressSpaceWaitHangupPhone", "88D6": "msgOpponentPickingGameMap", "88F0": "msgOpponentReadyForNewGame", "890C": "mapNameAlphabet", "892C": "gameTypeDefaultsSide0", "8945": "gameTypeDefaultsSide1", "895E": "msgOpponentUsingNonStandardGame", "897F": "messageBlockTail" } }, "notes": { "8800": { "unit": "game/messages8800", "block": [ "$8800-$89FF - THE MODEM STATUS MESSAGE BLOCK. Track 29 sectors 9-10, 512 bytes, pure data: no", "code, no pointers, nothing in it is relocated. It is read by loadStringsAndCommTail ($1060),", "which is entered by fall-through from loadOverlayA ($1047) at start-up and called directly from", "ensureOverlayAResident ($103B) every time the game returns to the main menu, so the whole block is", "re-read from disk before any of it can be needed again.", "", "Contents (every byte of the file is accounted for):", " $8800-$890B eleven bit-7-terminated message strings (the twelfth is at $895E)", " $890C-$892B mapNameAlphabet - the 32 characters a map name / map seed is written in", " $892C-$8944 gameTypeDefaultsSide0 - 5 x 5 scenario parameter defaults for side 0", " $8945-$895D gameTypeDefaultsSide1 - the same 5 x 5 table for side 1", " $895E-$897E the twelfth string, 'OPPONENT USING NON-STANDARD GAME.'", " $897F-$89FF 129 bytes of unreferenced filler (see messageBlockTail)", "", "Strings are bit-7-terminated (the last character has $80 added: $AE = '.', $A0 = ' ') and are", "reached by message id through the split pointer pair messagePtrLoTable ($0500) /", "messagePtrHiTable ($0524) in the $0400 block: ids $04, $0B, $10, $17, $18, $1A and $1F point in", "here permanently, and the two run-time slots $20/$21 (stored as $FFFF on disk) are aimed at", "strings in this block by setMessageSlot ($C924). $895E is not an id at all - its address is built", "from two immediates at $79DF/$79E1.", "", "WARNING - almost every D_88xx / D_89xx label in this listing is a SOLO TRAINER address, not a", "string boundary. Once a solo game starts, initTrainerAi ($EC0F) and the resident AI at $E000 use", "$8800-$897C as their work RAM and overwrite this whole block:", " $8800+ord 50 bytes replan / last-order clock per AI unit (ordinal = unit index - 50)", " $8832+ord 50 bytes group id per AI unit ($FF = none); addressed as $8800,absoluteUnitIndex", " $8864+ord 50 bytes 'has reached the enemy end zone' flag; = $8832,absoluteUnitIndex", " $8896 10 bytes staged command ($8896-$8899) and the 8-way threat/direction ring", " $88A0 50 bytes the outgoing order queue", " $88D2 $88DE $88EA $88F6 $8902 $890E $891A $8926 $8932 $893E ten 12-entry per-group arrays", " $894A 8 bytes heading search order - read at $E7F4 but never written by anything", " $8952-$897C the AI's scalar variables", "The AI stops at $897C: $897D-$89FF is the only part of the block it never touches. Clobbering the", "rest is safe because a modem game never loads the trainer, the map-name alphabet is only used by", "the setup overlay (which has finished by then), applyGameTypeDefaults has already read the two 5x5", "tables, and the menu path re-reads the block from disk. The per-byte notes below give both", "readings wherever a trainer label lands inside a string or a table row.", "", "Boot-time scratch: verifyProgramChecksum ($0461, in the $0400 block) parks its raw checksum sum in", "$8800 at $049D. That happens long before this block is loaded over it and nothing reads it back.", "", "msgGetOpponentOnPhoneAnd - 'GET OPPONENT ON PHONE AND...', 28 bytes. Text of run-time message", "slot $20 while a modem game is being set up: startGameFromSetup patches slot $20 to $8800 at", "$0B17-$0B1D and slot $21 to $881C, then calls waitForAnswerOriginateKey ($0CBD). That only", "happens when the previous game was a solo game and this one is a modem game." ] }, "881C": { "unit": "game/messages8800", "block": [ "msgPressAOrOAndSetModem - 'PRESS A OR O AND SET MODEM.', 27 bytes. The second line of the same", "prompt: run-time message slot $21, patched at $0B20-$0B26. A = answer, O = originate; the modem", "driver's hot keys pick which end dials. The string is cut in three below because the trainer's", "own tables start at $8831 and $8832." ] }, "8831": { "unit": "game/messages8800", "line": "'M'. Also the trainer's replan clock for AI unit ordinal 49 - its COMCEN - the last entry of the 50-byte array based at $8800" }, "8832": { "unit": "game/messages8800", "line": "'ODEM' + $AE terminator. Also the base of the trainer's 50-byte group-id array ($8832-$8863), which the resident AI addresses as $8800,absoluteUnitIndex" }, "8837": { "unit": "game/messages8800", "block": [ "msgPressSpaceWhenReady - 'PRESS SPACE WHEN READY.', 23 bytes. showModemSetupPrompt ($0C8C) puts", "the caller's string into message slot $20 and this one into slot $21 ($0C93: LDA #$37 / LDY #$88)", "before waiting for the space bar. Its only caller is the version-mismatch path at $79E3." ] }, "884E": { "unit": "game/messages8800", "block": [ "msgTrainerName - 'TRAINER ', 10 bytes ($A0 = the trailing space that carries the terminator).", "Not a status message: it is the opponent's name. exchangePlayerNamesAndSettings ($ED43 in the", "modem driver) skips the whole name handshake in solo mode and copies these ten characters into", "opponentPlayerName ($FFE8) with the loop at $ED4A, so the console shows TRAINER where a human", "opponent's typed name would be. Exactly ten bytes because the name fields are ten characters." ] }, "8858": { "unit": "game/messages8800", "block": [ "msgOpponentReady - 'OPPONENT READY.', 15 bytes. Message id $04 (messagePtrLoTable[$04] = $58,", "messagePtrHiTable[$04] = $88). Queued by getPauseClearMask at $52C0 when a session-control or", "setup-ready command from the opponent clears his 'not ready' bit (bit 1 of the pause byte)." ] }, "8863": { "unit": "game/messages8800", "line": "'A'. Also the last entry (ordinal 49, the AI COMCEN) of the trainer's group-id array" }, "8864": { "unit": "game/messages8800", "line": "'DY' + $AE. Also the base of the trainer's 50-byte 'reached the enemy end zone' flags ($8864-$8895)" }, "8867": { "unit": "game/messages8800", "block": [ "msgWaitingForOpponent - 'WAITING FOR OPPONENT...', 23 bytes. Message id $0B, pinned as the", "persistent status line ($92A1) at $572E in the packet-exchange wait and at $C7CC in the text", "engine; cleared again at $5760 / $7C18." ] }, "887E": { "unit": "game/messages8800", "block": [ "msgWaitingForConnection - 'WAITING FOR CONNECTION...', 25 bytes. Message id $10, pinned by", "waitForConnection ($1B7C) while it spins in waitForCarrier waiting for the modem to report", "carrier. On a solo game that routine returns before ever showing it." ] }, "8895": { "unit": "game/messages8800", "line": "'.'. Also the last entry (ordinal 49) of the trainer's end-zone flag array" }, "8896": { "unit": "game/messages8800", "line": "'.' + $80 = the string terminator. Also the trainer's scratch area: the staged command at $8896-$8899 and the 8-way threat/direction ring at $8896-$889D" }, "8897": { "unit": "game/messages8800", "block": [ "msgPickUpPhoneThenPressSpace - 'PICK UP PHONE THEN PRESS SPACE.', 31 bytes. Message id $17,", "shown by hangUpModemSetState ($1B42) through showMessageWaitForSpace on the modem build only", "(commBuildId $E030 negative = trainer, and then the prompt is skipped). This is the voice-pause /", "hang-up direction: the player picks the handset up, then space releases the modem." ] }, "8898": { "unit": "game/messages8800", "line": "'I'. Also a byte of the trainer's staged command ($8896 opcode, $8897-$8899 arguments)" }, "8899": { "unit": "game/messages8800", "line": "'CK UP P'. $8899 is also the last argument byte of the trainer's staged command" }, "88A0": { "unit": "game/messages8800", "line": "'HONE THEN PRESS SPACE.' - the rest of the string. Also the base of the trainer's 50-byte outgoing order queue ($88A0-$88D1)" }, "88B6": { "unit": "game/messages8800", "block": [ "msgPressSpaceWaitHangupPhone - 'PRESS SPACE, WAIT, HANGUP PHONE.', 32 bytes. Message id $18,", "shown by openCommLink ($1B69) before it brings the line up: space puts the modem into data mode,", "the player waits for it to take the line and then puts the handset down. The opposite direction", "to message $17 above - the two are easy to mix up and the earlier survey text had them swapped." ] }, "88D2": { "unit": "game/messages8800", "line": "'ONE' + $AE. Also the base of the trainer's 12-entry group target-column array ($88D2-$88DD)" }, "88D6": { "unit": "game/messages8800", "block": [ "msgOpponentPickingGameMap - 'OPPONENT PICKING GAME/MAP.', 26 bytes. Message id $1A, pinned at", "$7996 in the setup overlay while side 1 waits for side 0's map seed (command $8B/$A6) to arrive;", "cleared at $7C23 as soon as the seed is in." ] }, "88DE": { "unit": "game/messages8800", "line": "' PICKING GAM'. Also the trainer's 12-entry group target-row array ($88DE-$88E9)" }, "88EA": { "unit": "game/messages8800", "line": "'E/MAP' + $AE. Also the trainer's 12-entry group centre-column array ($88EA-$88F5)" }, "88F0": { "unit": "game/messages8800", "block": [ "msgOpponentReadyForNewGame - 'OPPONENT READY FOR NEW GAME.', 28 bytes. Message id $1F, queued at", "$56E8 when a session-control packet with reason 2 arrives; the frame counter zp_46 is used as a", "crude rate limit so the announcement cannot repeat more than about every 768 frames." ] }, "88F6": { "unit": "game/messages8800", "line": "'NT READY FOR'. Also the trainer's 12-entry group centre-row array ($88F6-$8901)" }, "8902": { "unit": "game/messages8800", "line": "' NEW GAME.' - the rest of the string. Also the trainer's 12-entry group member-count array ($8902-$890D), whose last two entries fall on the first two characters of mapNameAlphabet" }, "890C": { "unit": "game/messages8800", "block": [ "mapNameAlphabet - the 32-character alphabet ' ABCDEFGHIJKLMNOPQRSTUVWXYZ+-*/=', one character per", "5-bit code 0-31. A map's name IS its 24-bit seed written in base 32: enterMapName searches this", "table backwards from index $1F to validate each typed character ($779B), packNameIntoSeed shifts", "the five 5-bit codes into scenarioSeed0/1/2 ($7804) and seedToMapName spells a seed back out", "through the same table ($785D). Five characters x 5 bits = 25 bits, so the top bit of the first", "character is dropped when the name is packed. The manual's map list and the 32 x 3-byte", "protection table at $0401 hold the same numbers, which is why 'IDENTIFY THIS MAP' works and why", "two players can compare a five-letter name to be sure of playing the same battlefield. All three", "readers live in the setup overlay, which is gone before the trainer reuses these bytes." ] }, "890E": { "unit": "game/messages8800", "line": "alphabet entries 2-13, 'BCDEFGHIJKLM'. Also the first of the trainer's four 12-entry group formation-span arrays ($890E-$8919)" }, "891A": { "unit": "game/messages8800", "line": "alphabet entries 14-25, 'NOPQRSTUVWXY'. Also the trainer's second group formation-span array ($891A-$8925)" }, "8926": { "unit": "game/messages8800", "line": "alphabet entries 26-31, 'Z+-*/='. Also the trainer's third group formation-span array ($8926-$8931)" }, "892C": { "unit": "game/messages8800", "block": [ "gameTypeDefaultsSide0 - the scenario parameter defaults for side 0: five rows of five bytes,", "row-major, read a row at a time by readDefaultAndAdvanceRow ($7CBF), which indexes the row with", "the game-type column in Y and then adds 5 to the pointer. selectSideDefaultsTable ($7CAE) points", "zp_48/zp_49 here for side 0 and at $8945 for side 1; applyGameTypeDefaults ($7C2E) runs the whole", "table twice, once per side, right after both machines have agreed the game type ($79E9).", "", "Column = the game type ($0BA3 bits 0-2) folded to 0-4 by the code at $7C34-$7C44:", " 0 SCRIMAGE 1 QB SNEAK 2 THE BOMB / FACE-OFF / SLUGGERS 3 FULL WAR 4 DEFENDER", "The same column also indexes setupPhaseModeTable ($7C29) -> setupPhaseMode $92C6.", "Row 0 -> terrainPointsBySide $92AC[absolute side] TERRAIN PTS, 0 disables territory scoring", "Row 1 -> recyclerMode $92A6[absolute side] 0 FULL, 1 HALF, 2 NONE", "Row 2 -> comcenSpeedClass $92B4[relative side] COMCEN SPEED setting 0-3, mapped to engine", " class 4/0/1/5 at $7B6E; 0 = comcen immobile", "Row 3 -> dronesLeft $92AE[relative side] number of drones", "Row 4 -> missilesLeft $92B0[relative side] number of missiles", "(rows 0-1 are stored under the absolute side number, rows 2-4 under 0 = this machine, 1 = the", "opponent). Side 0's full table:", " SCRIMAGE QB SNEAK BOMB/FACE-OFF/SLUG FULL WAR DEFENDER", " TERRAIN PTS 1 0 1 1 1", " RECYCLER NONE NONE NONE HALF NONE", " COMCEN SPEED 2 3 2 1 0", " DRONES 0 0 3 5 2", " MISSILES 0 8 12 20 20", "The rows are chopped up below because four trainer tables start in the middle of them." ], "line": "row 0, TERRAIN PTS by game type -> $92AC[0]" }, "8931": { "unit": "game/messages8800", "line": "row 1 column 0: RECYCLER for SCRIMAGE = 2 (NONE)" }, "8932": { "unit": "game/messages8800", "line": "row 1 columns 1-4: RECYCLER = NONE, NONE, HALF, NONE -> $92A6[0]. Also the trainer's fourth 12-entry group formation-span array ($8932-$893D)" }, "8936": { "unit": "game/messages8800", "line": "row 2, COMCEN SPEED setting 0-3 -> $92B4; QB SNEAK gives side 0 the fastest comcen (3) and DEFENDER an immobile one (0)" }, "893B": { "unit": "game/messages8800", "line": "row 3 columns 0-2: DRONES = 0, 0, 3" }, "893E": { "unit": "game/messages8800", "line": "row 3 columns 3-4: DRONES = 5 (FULL WAR), 2 (DEFENDER) -> $92AE. Also the base of the trainer's 12-entry group flag / start-delay array ($893E-$8949)" }, "8940": { "unit": "game/messages8800", "line": "row 4, MISSILES 0, 8, 12, 20, 20 -> $92B0" }, "8945": { "unit": "game/messages8800", "block": [ "gameTypeDefaultsSide1 - the same 5 x 5 table for side 1; same rows, same columns, same readers", "(selectSideDefaultsTable loads $8945 when X = 1). Values:", " SCRIMAGE QB SNEAK BOMB/FACE-OFF/SLUG FULL WAR DEFENDER", " TERRAIN PTS 1 1 1 1 0", " RECYCLER NONE NONE NONE HALF HALF", " COMCEN SPEED 2 2 2 1 1", " DRONES 0 0 3 5 5", " MISSILES 0 8 12 20 8", "Two columns are asymmetric, not one: QB SNEAK (side 0 gets no terrain point and a speed-3 comcen,", "side 1 one terrain point and a speed-2 comcen - the sneaking side is the one that has to move", "fast) and DEFENDER (side 0: immobile comcen, no recycler, 2 drones and 20 missiles; side 1: a", "moving comcen, a HALF recycler, 5 drones and 8 missiles - so side 0 is probably the dug-in", "defender and side 1 the attacker). The earlier survey text called DEFENDER the only asymmetric", "column, which the bytes disprove.", "", "This table is also the source of the trainer's oddest bug. buildDirectionPreference ($E7E3)", "reads its eight compass offsets from $894A, but nothing in the whole disassembly ever writes", "$894A-$8951: at run time they still hold row 1 and the first three bytes of row 2 of this table,", "$02,$02,$02,$01,$01,$02,$02,$02, so all eight 'candidate headings' collapse to heading+1 and", "heading+2 and the AI never spirals its search outwards. $897A (the comcen recall threshold) is", "the other byte the resident AI only reads, but that one is initialised to 5 by the track-29 $EC00", "setup overlay at $EC44, so only $894A is really left holding message-block data." ], "line": "row 0 columns 0-3: TERRAIN PTS = 1, 1, 1, 1" }, "8949": { "unit": "game/messages8800", "line": "row 0 column 4: DEFENDER TERRAIN PTS = 0 -> $92AC[1]. Also the last entry of the trainer's group flag array" }, "894A": { "unit": "game/messages8800", "line": "row 1, RECYCLER = NONE, NONE, NONE, HALF, HALF -> $92A6[1]. $894A-$8951 (this row plus the first three bytes of the next) is also the trainer's 8-entry heading search table, which nothing ever writes - see the block comment above" }, "894F": { "unit": "game/messages8800", "line": "row 2 columns 0-2: COMCEN SPEED = 2, 2, 2" }, "8952": { "unit": "game/messages8800", "line": "row 2 column 3 (FULL WAR): COMCEN SPEED 1. Trainer scalar: length of the outgoing order queue at $88A0" }, "8953": { "unit": "game/messages8800", "line": "row 2 column 4 (DEFENDER): COMCEN SPEED 1 -> $92B4[1]. Trainer scalar: highest group id allocated so far" }, "8954": { "unit": "game/messages8800", "line": "row 3 column 0 (SCRIMAGE): DRONES 0. Trainer scalar: number of groups" }, "8955": { "unit": "game/messages8800", "line": "row 3 column 1 (QB SNEAK): DRONES 0. Trainer scalar: id of the group currently being planned" }, "8956": { "unit": "game/messages8800", "line": "row 3 column 2 (BOMB/FACE-OFF/SLUGGERS): DRONES 3. Trainer scalar: low byte of that group's combat value" }, "8957": { "unit": "game/messages8800", "line": "row 3 column 3 (FULL WAR): DRONES 5. Trainer scalar: high byte of the group combat value" }, "8958": { "unit": "game/messages8800", "line": "row 3 column 4 (DEFENDER): DRONES 5 -> $92AE[1]. Trainer scalar: the clamped column delta of a group's target" }, "8959": { "unit": "game/messages8800", "line": "row 4 column 0 (SCRIMAGE): MISSILES 0. No label here because the trainer never touches this byte" }, "895A": { "unit": "game/messages8800", "line": "row 4 column 1 (QB SNEAK): MISSILES 8. Trainer scalar: 'this group contains slow units' flag" }, "895B": { "unit": "game/messages8800", "line": "row 4 columns 2-3: MISSILES 12 and 20. $895B is also the trainer's desired advance distance; $895C is another byte the trainer never touches" }, "895D": { "unit": "game/messages8800", "line": "row 4 column 4 (DEFENDER): MISSILES 8 -> $92B0[1]. Trainer scalar: the group its comcen escorts" }, "895E": { "unit": "game/messages8800", "block": [ "msgOpponentUsingNonStandardGame - 'OPPONENT USING NON-STANDARD GAME.', 33 bytes, the twelfth and", "last string of the block. It has no message id: checkOpponentVersion loads its address from two", "immediates (LDA #$5E / LDY #$89 at $79DF) and hands it to showModemSetupPrompt, which shows it in", "slot $20 above 'PRESS SPACE WHEN READY.' ($8837). It appears when the build stamp exchanged by", "exchangePlayerNamesAndSettings comes back different from the $DB stored at $79CC - i.e. the two", "players are running different builds, or one of them a cracked/patched disk.", "", "The listing below breaks into one byte per line because the trainer's scalar variables sit on top", "of the text, one label per byte. The ASCII in the address column still spells the message out.", "As documented in game/trainerAiE000 / game/trainerSetupEC00 - a few of them only at medium", "confidence there - they are:", " $895E/$895F centre column/row of the group the AI comcen escorts", " $8960 best heading $8961 enemy proximity $8962 threat score $8963 0 attack / $FF defend", " $8964 home-defence group $8965 comcen action budget $8966 skill/play level,", " also reused as scratch for group flags and a per-round budget", " $8967 comcen action budget (repair path) $8968 orders issued this round", " $8969/$896A rally point column/row $896B rally refresh counter (probably)", " $896C 'the human comcen has been seen'", " $896D replan urgency $896E alert clock $896F drone heading", " $8970 missile reload timer $8971 comcen damage $8972 unit currently shooting the comcen", " $8973 drone fuel $8974/$8975 drone column/row $8976 drone target unit", " $8977 drone-overlay state $8978 drone detonate threshold $8979 strike score threshold", " $897A comcen recall threshold (set to 5 by $EC44; the resident AI only reads it)", " $897B comcen hit $897C comcen hit recently / comcen is moving" ] }, "897D": { "unit": "game/messages8800", "line": "'E' + $AE terminator - the last two bytes of the mastered message data, and the first two bytes of the block that the solo trainer's work area (which ends at $897C) does not overwrite" }, "897F": { "unit": "game/messages8800", "block": [ "messageBlockTail - 129 unreferenced bytes, the rest of track 29 sector 10 after the last string.", "No code anywhere reads or writes them, and they are re-loaded from disk with the block every time.", "They are not random: from $8980 the bytes are 8-byte groups of alternating $00/$FF, i.e. exactly", "what a 50-percent horizontal-stripe dither looks like in a C64 bitmap laid out in 8-byte character", "cells, with the phase inverting every 16 bytes (every two cells) and a dozen stray bytes ($03,", "$F3, $BF, $0C, $DC, $B3, $3F, $C0, $CC, $01, $DF) that look like the edge of a shape drawn over", "it. It is rendered as bit art below so the pattern can be seen. Probably a fragment of screen", "memory left in the mastering machine's buffer when the sector was written; the same kind of", "dither (different bytes - the two do not match) ends the $9300 data block at $9FD1. The pattern", "occurs nowhere else on the disk." ], "line": "$00 - odd byte before the first whole character cell; the mastered message data ended at $897E" }, "8980": { "unit": "game/messages8800", "line": "16 consecutive 8-byte character cells of dither, unused" } }, "dataTypes": { "8800": [ "text", 28 ], "881C": [ "text", 21 ], "8831": [ "text", 1 ], "8832": [ "text", 5 ], "8837": [ "text", 23 ], "884E": [ "text", 10 ], "8858": [ "text", 11 ], "8863": [ "text", 1 ], "8864": [ "text", 3 ], "8867": [ "text", 23 ], "887E": [ "text", 23 ], "8895": [ "text", 1 ], "8896": [ "text", 1 ], "8897": [ "text", 1 ], "8898": [ "text", 1 ], "8899": [ "text", 7 ], "88A0": [ "text", 22 ], "88B6": [ "text", 28 ], "88D2": [ "text", 4 ], "88D6": [ "text", 8 ], "88DE": [ "text", 12 ], "88EA": [ "text", 6 ], "88F0": [ "text", 6 ], "88F6": [ "text", 12 ], "8902": [ "text", 10 ], "890C": [ "text", 2 ], "890E": [ "text", 12 ], "891A": [ "text", 12 ], "8926": [ "text", 6 ], "895E": [ "text", 1 ], "897D": [ "text", 2 ], "892C": [ "grid", 5, 5 ], "8931": [ "grid", 1, 1 ], "8932": [ "grid", 4, 4 ], "8936": [ "grid", 5, 5 ], "893B": [ "grid", 3, 3 ], "893E": [ "grid", 2, 2 ], "8940": [ "grid", 5, 5 ], "8945": [ "grid", 4, 4 ], "8949": [ "grid", 1, 1 ], "894A": [ "grid", 5, 5 ], "894F": [ "grid", 3, 3 ], "8959": [ "grid", 1, 1 ], "895B": [ "grid", 2, 2 ], "897F": [ "byte", 1 ], "8980": [ "bitmap", 128 ] } }