{ "chunk": "game_table_0400_0400", "unit": "game/table_0400", "routines": [ {"addr":"0461", "name":"verifyProgramChecksum", "summary":"Boot-time integrity check, entered by the boot loader's obfuscated RTS (the stack was rigged to hold $0460). Saves randomSeed ($09C3) in zp_19 and zeroes it so the sum is seed independent, then runs a self-modifying ADC/EOR/ROL checksum over $0402-$87FF (132 pages; the operand high bytes at $047C and $0484 are incremented per page and end at $88), adds the carry counter zp_1B, stores the sum at $8800, stores sum EOR $0400 ($B0) in programChecksum ($0B7E), restores randomSeed and does JMP $0800 with Y=2. The ADC carry is deliberately chained from one word to the next (the CLC at $0479 is outside the loop) and the PHP/ASL/BCC/ORA/PLP block is a ROL that preserves that carry.", "inputs":"memory $0402-$87FF, $09C3 (random seed, zeroed during the sum), $0400 = $B0", "outputs":"$8800 = raw sum ($69 on this disk image), $0B7E = sum EOR $B0 ($D9), $09C3 restored, zp_19/zp_1A/zp_1B clobbered, $047C/$0484 left at $88, JMP $0800 with Y=2 and S=$FE", "confidence":"high"}, {"addr":"06BA", "name":"switchToRequestedScreen", "summary":"Called from every screen's main loop to honour an F1/F3/F5/F7 screen change. Returns immediately when the chat editor is active (D_929C != 0), when zp_66 has bit 7 set (no request) or when the request equals the current screen. Otherwise it makes the request current, resets the stack to $FD (so it never returns to the caller), turns all sprites off (VIC_SPR_ENA and the shadows $9020/$9022/$9023/$9024), clears $92F9 unless $0B9B bit 7 is set, redraws the bitmap frame and jumps to the new screen's entry point.", "inputs":"zp_66 (requested screen 0-3, $FF = none), D_90FB (current screen), D_929C (chat state), D_0B9B bit 7", "outputs":"D_90FB updated, sprites disabled, S=$FD, never returns: JMPs to enterMapScreen (0, also sets $9020=$C4), runMissileScreen (1), enterDroneScreen (2) or enterMainMenu (3)", "confidence":"high"}, {"addr":"0709", "name":"runComcenStunnedBlackout", "summary":"The 'COMCEN STUNNED!' cut-scene, entered by JMP from $4546 when the command centre status byte D_922A has reached $FF and the game is not in solo/film mode. It releases drone control, wipes the message line and screen, resets the stack, then runs the engine unattended (updateGameFrame) with input locked out and D_922A bit 6 set (which keeps sprites off) until unitOrderIndex ($9162) comes back to the value it had on entry, i.e. exactly one full 100-unit update round. It then queues message $0C ('COMCEN STUNNED!'), clears the lockout, masks D_922A with $8F and re-enters whichever screen was active.", "inputs":"D_9162 (captured into the CMP operand at $073E), D_922A, D_90FB (current screen), D_92C9 and D_0BA8 (abort conditions), D_90FC (per-frame gate)", "outputs":"one blacked-out engine round; inputLockoutTimer set then cleared; D_922A bits 4-6 cleared; message $0C queued via sub_C566; never returns: jumps to runMapMainLoop (after drawOverviewMap/drawViewport), missileScreenResume, resetDroneScreen or enterMainMenuKeepSelection", "confidence":"medium"} ], "variables": [ {"addr":"19","scope":"zp","name":"savedRandomSeed","meaning":"verifyProgramChecksum: saved copy of randomSeed ($09C3) while the sum is computed","confidence":"high"}, {"addr":"1A","scope":"zp","name":"checksumSpareSeed","meaning":"verifyProgramChecksum stores SID_OSC3 ($D41B) AND $7F here and never reads it back - a dead store (probably a second random seed in an earlier version)","confidence":"high"}, {"addr":"1B","scope":"zp","name":"checksumCarryCount","meaning":"verifyProgramChecksum: count of ADC carries, added to the sum at the end","confidence":"high"}, {"addr":"66","scope":"zp","name":"requestedScreenId","meaning":"last key from the IRQ poll: $FF = none, 0-3 = F1/F3/F5/F7 = requested screen; consumed by switchToRequestedScreen","confidence":"high"}, {"addr":"09C3","scope":"abs","name":"randomSeed","meaning":"SID noise sample stored by the boot loader; it is physically the immediate operand of the LDA #$xx at $09C2 in the map-identification protection, and verifyProgramChecksum zeroes/restores it so the checksum is seed independent","confidence":"high"}, {"addr":"0400","scope":"abs","name":"scratchPage0400","meaning":"start of the 256-byte multi-purpose scratch page; at boot it is $B0, the constant XORed into the checksum. After boot the whole page is reused: 100-entry unit scan list / group member counts (main), 80-entry blob column stack (map generator, ovl A $71CA), threat radius table (comm module $E6EC/$EC87)","confidence":"high"}, {"addr":"0410","scope":"abs","name":"groupWorstTerrainCost","meaning":"16-byte scratch (inside the scratch page): worst terrain cost of each group ($48ED/$4942/$493D)","confidence":"medium"}, {"addr":"0420","scope":"abs","name":"groupBestRate","meaning":"16-byte scratch: slowest movement rate of each group ($48F2/$4960/$495B)","confidence":"medium"}, {"addr":"0430","scope":"abs","name":"groupMoveTableOld","meaning":"16-byte scratch: previous-round copy of the group move table ($48E2/$4978)","confidence":"medium"}, {"addr":"0432","scope":"abs","name":"scanDistanceList","meaning":"scratch list parallel to $0400: squared distance of each scanned unit; also the blob row stack area of the map generator","confidence":"medium"}, {"addr":"0450","scope":"abs","name":"blobStackRow","meaning":"80-entry row stack of the terrain-blob grower in overlay A ($71D1/$72C0/$71F0)","confidence":"medium"}, {"addr":"0464","scope":"abs","name":"scanDirectionList","meaning":"scratch parallel to $0400 (direction 0-3 to each scanned unit); also the 12-entry high-byte half of a 16-bit statistics accumulator whose low bytes are at $88D2 (sub-overlay $EC00, $ED95/$EDF5)","confidence":"medium"}, {"addr":"0478","scope":"abs","name":"statAccumHi1","meaning":"12-entry high-byte half of the second 16-bit statistics accumulator (low bytes $88DE), cleared at $ED98 and incremented at $EE0B","confidence":"medium"}, {"addr":"048C","scope":"abs","name":"statAccumHi2","meaning":"12-entry high-byte half of the third statistics accumulator (low bytes $88EA), $ED9B/$EE1F","confidence":"medium"}, {"addr":"04A0","scope":"abs","name":"statAccumHi3","meaning":"12-entry high-byte half of the fourth statistics accumulator (low bytes $88F6), $ED9E/$EE33; also the blob direction-flag stack of the map generator. NOTE: XREF.txt names this label writeSectorToDisk, which is a stale name borrowed from the 1541 drive code","confidence":"medium"}, {"addr":"04C8","scope":"abs","name":"tileBuffer","meaning":"32-byte buffer holding one 2x2-character map tile ($04C8 top-left, $04D0 top-right, $04D8 bottom-left, $04E0 bottom-right) built by renderMapTile and copied to the bitmap by drawViewport","confidence":"high"}, {"addr":"0B7D","scope":"abs","name":"inputLockoutTimer","meaning":"frames during which the IRQ ignores keyboard/joystick; runComcenStunnedBlackout keeps it non-zero for the whole blackout round and clears it afterwards","confidence":"high"}, {"addr":"0B7E","scope":"abs","name":"programChecksum","meaning":"result of verifyProgramChecksum (sum of $0402-$87FF EOR $B0). Sent to the opponent in the version-handshake packet ($E02A, comm build 2 at $ED89); the disk image holds the mastered value $DB but the code actually computes $D9","confidence":"high"}, {"addr":"0B9B","scope":"abs","name":"playModeFlags","meaning":"bit 7 = solo trainer / film playback: makes switchToRequestedScreen keep $92F9 and stops $4546 from entering the COMCEN blackout","confidence":"high"}, {"addr":"0BA8","scope":"abs","name":"gameOverFlags","meaning":"non-zero ends the blackout loop early (game over / no game running)","confidence":"medium"}, {"addr":"8800","scope":"abs","name":"checksumScratch","meaning":"receives the raw checksum sum at boot; overwritten later when the message strings are loaded to $8800-$89FF","confidence":"high"}, {"addr":"9020","scope":"abs","name":"spriteXMsbShadow","meaning":"shadow of VIC $D010; cleared on every screen switch, set to $C4 when entering the battlefield map","confidence":"high"}, {"addr":"9022","scope":"abs","name":"spriteEnableShadow","meaning":"shadow of VIC $D015, cleared together with the register on a screen switch","confidence":"high"}, {"addr":"9023","scope":"abs","name":"spriteXExpandShadow","meaning":"shadow of VIC $D01D, cleared on a screen switch","confidence":"high"}, {"addr":"9024","scope":"abs","name":"spriteYExpandShadow","meaning":"shadow of VIC $D017, cleared on a screen switch","confidence":"high"}, {"addr":"90FB","scope":"abs","name":"currentScreenId","meaning":"screen currently running: 0 battlefield map, 1 missile screen ($7587), 2 drone screen ($7FE6), 3 console/main menu","confidence":"high"}, {"addr":"90FC","scope":"abs","name":"frameGateCounter","meaning":"non-zero means the current frame's work is not finished; the blackout loop spins on it before calling updateGameFrame","confidence":"medium"}, {"addr":"9162","scope":"abs","name":"unitOrderIndex","meaning":"index of the next unit in the processing order; the blackout loop copies it into the CMP operand at $073E and runs until it wraps back to that value","confidence":"high"}, {"addr":"922A","scope":"abs","name":"comcenStatus","meaning":"command-centre status: bits 0-3 stun counter, bit 7 stunned; value $FF triggers the blackout, which sets bit 6 (blocks sprite enabling) and finally masks with $8F","confidence":"medium"}, {"addr":"929C","scope":"abs","name":"chatState","meaning":"non-zero while the player is typing a chat line; blocks screen switching","confidence":"high"}, {"addr":"92C9","scope":"abs","name":"pauseTimeoutCounter","meaning":"non-zero aborts the blackout loop (pause/timeout in progress)","confidence":"medium"}, {"addr":"92F9","scope":"abs","name":"droneStrikeFlags","meaning":"drone/special-weapon state flags; cleared on a screen switch unless $0B9B bit 7 is set","confidence":"medium"}, {"addr":"9253","scope":"abs","name":"chatTextBuffer","meaning":"raw characters of the chat line being typed; mirrored into $0549.. (with bit 7 on the last one) by $13EF so the message printer can display it","confidence":"high"} ], "dataBlocks": [ {"addr":"0400","length":1,"type":"byteTable","name":"checksumXorConstant","description":"$B0 - the constant that verifyProgramChecksum XORs into the final sum before storing it at $0B7E. Excluded from the checksummed range (which starts at $0402)."}, {"addr":"0401","length":96,"type":"byteTable","name":"mapNameSeedTable","description":"32 x 3 bytes used by the manual look-up protection at $09A6: the random seed ($09C3, the immediate operand of the LDA at $09C2) AND $1F picks an entry, the triple is copied to $0B95-$0B97, generateBattlefieldMap draws that map and the player must type its name from the manual; the typed name is packed into zp_5A-zp_5C and compared with the same three bytes. So the triple is both the map generator seed and the expected answer. Also the first byte checksummed is $0402, so entry 0's first byte is outside the sum."}, {"addr":"04AE","length":82,"type":"unknown","name":"scratchPageTail","description":"$00 filler on disk; at run time part of the shared $0400 scratch page (tile buffer at $04C8-$04E7, statistics accumulators, blob stacks)."}, {"addr":"0500","length":36,"type":"byteTable","name":"messagePtrLoTable","description":"Low bytes of the 36 status-message string pointers, indexed by message id $00-$23; read at $C536/$C5E5 and patched at run time by sub_C924 (ids $1E, $20, $21 are $FFFF placeholders on disk)."}, {"addr":"0524","length":36,"type":"byteTable","name":"messagePtrHiTable","description":"High bytes of messagePtrLoTable. Ids resolve to strings in this file ($0548-$0615), in overlay B ($8707-$87F5) and in the loaded message block ($8858-$88FF)."}, {"addr":"0548","length":36,"type":"text","name":"chatMessageLine","description":"Message id $00: the chat/typed line. $0548 is a leading marker glyph ($1E cursor; $9E by sub_C62A to terminate immediately = blank line), $0549-$056B hold up to 35 characters mirrored from $9253 with bit 7 set on the last one. On disk it is 35 spaces plus $A0."}, {"addr":"056C","length":10,"type":"text","name":"msgWorking","description":"Message id $01 'WORKING...' (bit-7 terminated), sound $80 = silent."}, {"addr":"0576","length":13,"type":"text","name":"msgGameAborted","description":"Message id $03 'GAME ABORTED.', sound $03."}, {"addr":"0583","length":16,"type":"text","name":"msgNotAGameFilm","description":"Message id $09 'NOT A GAME FILM!', sound $03 (film load rejected)."}, {"addr":"0593","length":33,"type":"text","name":"msgSetupUnitsPressF1WhenDone","description":"Message id $0A 'SETUP UNITS. PRESS F 1 WHEN DONE.', silent."}, {"addr":"05B4","length":28,"type":"text","name":"msgTimeoutRunStopToResume","description":"Message id $19 'TIMEOUT. RUN/STOP TO RESUME.', sound $19."}, {"addr":"05D0","length":7,"type":"text","name":"msgResumed","description":"Message id $1B 'RESUMED', sound $19."}, {"addr":"05D7","length":18,"type":"text","name":"msgTheBattleBegins","description":"Message id $1C 'THE BATTLE BEGINS!', sound $03."}, {"addr":"05E9","length":17,"type":"text","name":"msgLastQtrWarning","description":"Message id $1D 'LAST QTR WARNING!', sound $19 (the game is played in quarters)."}, {"addr":"05FA","length":14,"type":"text","name":"msgPhoneTrouble","description":"Message id $22 'PHONE TROUBLE.', sound $15 (modem link failure)."}, {"addr":"0608","length":10,"type":"text","name":"msgTryAgain","description":"Message id $23 'TRY AGAIN.', sound $03."}, {"addr":"0612","length":36,"type":"byteTable","name":"messageSoundTable","description":"Sound effect number played when message id X is displayed (read at $C557, played through sub_C89A); $80 = no sound. Notable: $0E for the two 'we lost' messages, $0D for the two 'we won' messages, $19 for GAME OVER / TIMEOUT / RESUMED / LAST QTR WARNING, $15 for PHONE TROUBLE."}, {"addr":"0636","length":23,"type":"text","name":"optionSummaryTemplate","description":"' MENUS XXXX DAMAGE XXX.' (bit-7 terminated) - a rules summary line whose XXXX/XXX fields are overwritten in place by overlay A/B ($870C, $8739-$8751) with OFF,/ON, and FULL/HALF/NONE before it is displayed."}, {"addr":"064D","length":17,"type":"text","name":"optionWordTable","description":"Five bit-7 terminated option words at fixed offsets used to fill optionSummaryTemplate: +0 'OFF', +3 'ON', +5 'FULL', +9 'HALF', +13 'NONE'. XREF.txt calls this label eaCopyrightMessage, which is wrong."}, {"addr":"065E","length":10,"type":"text","name":"msgClicks","description":"' CLICKS:' - the score header printed by $C6BC (lda #$5E / ldy #$06 / jsr sub_C133). 'Clicks' are Modem Wars' score units."}, {"addr":"0668","length":7,"type":"text","name":"msgUs","description":"' US:' - printed straight after msgClicks by sub_C137, followed by the local player's 16-bit score from $92A2/$92A4."}, {"addr":"066F","length":8,"type":"text","name":"msgThem","description":"' THEM:' - the opponent's score label, printed by the same $C6B6 routine."}, {"addr":"0677","length":40,"type":"text","name":"msgTypeMapName","description":"Three lines separated by $0D: 'TYPE MAP NAME' / ' (F7 RANDOM)' / ' (F5 SYM/RND)' - the prompt of enterMapName (also used by the manual look-up protection at $09EF)."}, {"addr":"069F","length":27,"type":"text","name":"msgBtnToAccept","description":"Two lines separated by $0D: 'BTN TO ACCEPT' / '(F 1 TO FLIP)' - shown while the generated map is offered for acceptance."}, {"addr":"0770","length":40,"type":"byteTable","name":"tileVariantMaskTable","description":"Indexed by map cell value minus $40 (cells below $40 use cell-$20), covering cells $40-$67. The value is a mask ANDed with a scenario random number and added to the base tile by randomTileVariant ($7872, overlay A): cells $49 and $4B and $65 -> mask 1, cells $4D, $51, $59, $5D, $61 -> mask 3, everything else 0 (no variation)."}, {"addr":"0798","length":1,"type":"unknown","name":"tileVariantPad","description":"$00 pad between tileVariantMaskTable and unitGlyphBaseTable."}, {"addr":"0799","length":5,"type":"byteTable","name":"unitGlyphBaseTable","description":"Base glyph index of the 1-character overview-map symbol per unit type: $2C GRUNT, $30 RIDER, $34 BOOMER, $38 SPY, $3C COMCEN. The facing (unit flags AND 3) is added, then $2C is subtracted and the result doubled to index the glyph pairs ($28A0, $7512, $7538)."}, {"addr":"079F","length":75,"type":"byteTable","name":"terrainClassTable","description":"Map cell value -> terrain class 0-8. It is addressed as D_077E,y with the raw cell value, so the body starts at $077E+$21 = $079F and ends at $077E+$6B = $07E9: cells $21-$34 -> 3 (lower contour), $35-$3F -> $FF (invalid), $40 -> 0 (blank), $41-$54 -> 5 (upper contour), $55-$5C -> 1 (river), $5D-$60 -> 2 (forest), $61-$64 -> 4 (lower hill), $65-$66 -> 6 (summit), $67 -> 7 (recycler), $68-$6B -> 8. The same bytes are also read as D_07BE,x by $5BC8 with the index already reduced to cell-$40 (clamped to < $21)."}, {"addr":"07EA","length":9,"type":"byteTable","name":"terrainColourTable","description":"Colour-RAM nibble per terrain class 0-8: 7,3,5,8,8,8,8,0,0 (yellow plain, cyan river, green forest, orange contours/hills/summit, black for classes 7-8). Read at $27AB and $5BCB right after a terrainClassTable lookup."}, {"addr":"07F3","length":13,"type":"unknown","name":"table0400Tail","description":"$00 filler to the end of the page."} ], "misclassified": [ {"addr":"0461","length":77,"actual":"code", "evidence":"$0461-$04AD is the boot-time checksum routine, emitted as .byte because nothing in the disassembled image reaches it by a normal JSR/JMP: the boot loader lands there through a rigged RTS ($01FD=$60 pushed at $C1B1, $01FE=$04 stored by bootShowTitlePicture). It disassembles cleanly as LDA $09C3 / STA $19 / LDA $D41B / AND #$7F / STA $1A / LDX #$84 / LDA #$00 / STA $1B / TAY / INY / INY / STA $09C3 / CLC / loop{ADC $0400,Y / BCC / INC $1B / INY / EOR $0400,Y / PHP / ASL / BCC / ORA #$01 / PLP / INY / BNE} / INC $047C / INC $0484 / DEX / BNE / LDY #$02 / CLC / ADC $1B / STA $8800 / EOR $0400 / STA $0B7E / LDA $19 / STA $09C3 / JMP $0800. Re-implementing it in Python over extracted/bootImage.bin reproduces exactly the values found in the real VICE snapshot extracted/vice/ram_at_0800.bin ($8800=$69, $0B7E=$D9, $047C=$0484=$88)."} ], "insights": [ "The $0400 page has three lives. On disk (track 27 s13-16, copied from $6F00-$72FF by the boot loader) it holds the manual look-up protection table at $0401 and the checksum routine at $0461-$04AD. During the game the whole page is a shared scratch area (unit scan lists at $0400/$0432/$0464, group scratch $0410/$0420/$0430, 2x2 tile buffer $04C8-$04E7, blob stacks of the map generator, 16-bit statistics accumulators whose low halves are at $88D2-$8901). The default setup block (track 29 s6-8) is loaded to $0200-$04FF and overwrites the whole first half of it. Only $0500-$07FF is stable data.", "verifyProgramChecksum ($0461) sums $0402-$87FF - i.e. everything from the protection table up to and including the loaded overlay ($6F00-$87FF is overlay B at that moment) - with a carry-chained ADC + EOR + ROL word loop, then stores sum EOR $B0 in $0B7E. Verified against the real machine: the VICE snapshot has $8800=$69 and $0B7E=$D9. The disk image's own mastered byte at $0B7E is $DB and overlay A compares the value received from the opponent against the immediate $DB at $79CC, so on THIS d64 the check would fail - strong evidence that the image has been patched by a couple of bits somewhere in $0402-$87FF (a crack or a version difference). Confidence medium on the interpretation, high on the arithmetic.", "The checksum is a link-level anti-tamper measure, not just a boot check: comm build 2 puts $0B94 (settings/film version) in $E029 and $0B7E in $E02A, sends packet type 4 and computes $90F8 = received $E023 EOR $E022; overlay A preloads $90F8 with $DB and, if it differs after the handshake, prints the string at $895E 'OPPONENT USING NON-STANDARD GAME.'", "Complete status-message system: 36 ids ($00-$23) with the address split over messagePtrLoTable ($0500) / messagePtrHiTable ($0524) and a parallel sound id in messageSoundTable ($0612, $80 = silent). $C536 prints the string through sub_C3D8 and sets zp_72=$3C (60 frames); sub_C566 queues an id into the 8-entry priority ring at $9249; sub_C560 is the same but suppressed while $0B9B bit 7 (solo/film) is set. The texts are: 00 chat line, 01 WORKING..., 02 END OF FILM., 03 GAME ABORTED., 04 OPPONENT READY., 05 UNIT RECYCLED., 06/07/08 SPOTTED RECYCLER variants, 09 NOT A GAME FILM!, 0A SETUP UNITS. PRESS F 1 WHEN DONE., 0B WAITING FOR OPPONENT..., 0C COMCEN STUNNED!, 0D SPOTTED ENEMY COMCEN!, 0E SPY UNDER ATTACK!, 0F SPOTTED ENEMY SPY!, 10 WAITING FOR CONNECTION..., 11 WE LOST ON POINTS!, 12 WE WON ON POINTS!, 13 IT WAS A DRAW., 14 WE WERE KNOCKED OUT!, 15 WE WON BY A KNOCKOUT!, 16 GAME OVER., 17 PICK UP PHONE THEN PRESS SPACE., 18 PRESS SPACE, WAIT, HANGUP PHONE., 19 TIMEOUT. RUN/STOP TO RESUME., 1A OPPONENT PICKING GAME/MAP., 1B RESUMED, 1C THE BATTLE BEGINS!, 1D LAST QTR WARNING!, 1E/20/21 run-time slots patched by sub_C924, 1F OPPONENT READY FOR NEW GAME., 22 PHONE TROUBLE., 23 TRY AGAIN.", "Message id 0 is the chat line: $0548 is a marker glyph ($1E while typing, $9E to blank the line) and $0549.. is a bit-7 terminated mirror of the raw chat buffer at $9253, rebuilt character by character at $13EF. That is why the 'blank' string at $0548 is exactly 36 bytes - the width of the message panel.", "Screen dispatch: zp_66 holds the last key ($FF = none, 0-3 from F1/F3/F5/F7) and switchToRequestedScreen ($06BA), called from all four screen loops ($1463 console, $2033 map, $75C3 missile, $8040 drone), resets the stack and jumps to enterMapScreen / runMissileScreen / enterDroneScreen / enterMainMenu. It refuses to switch while the chat editor is active, so screens can never be changed mid-message.", "The terrain tables at $0770-$07F2 are addressed through three different biased bases and must not be treated as one table per label: tileVariantMaskTable at $0770 is indexed by cell-$40, terrainClassTable is indexed by the raw cell value from base $077E (body $079F-$07E9) and also read as $07BE,x with the index pre-reduced, and terrainColourTable at $07EA is indexed by the resulting class 0-8. Earlier surveys extended terrainClassTable past cell $6B and so read the colour table as if it were class data (that is where the bogus '$6C -> 7, $6D -> 3, $6E -> 5, $6F-$72 -> 8' entries come from).", "The manual look-up protection ($09A6) does not store a separate 'answer': the 3-byte entry picked from $0401 is copied to $0B95-$0B97, used as the seed for generateBattlefieldMap, and then compared with the packed name the player types (zp_5A-zp_5C). The entry is selected by 'LDA #seed / AND #$1F' where the immediate operand at $09C3 is the SID noise byte the boot loader poked in - so the byte the checksum has to neutralise is literally an instruction operand.", "runComcenStunnedBlackout ($0709) is the visible consequence of a COMCEN hit: control is taken away, the screen is cleared and the engine is stepped for exactly one full 100-unit round with sprites suppressed (D_922A bit 6) and input locked, then 'COMCEN STUNNED!' is queued and the previous screen is restored. It is skipped in solo/film mode ($0B9B bit 7)." ] }