7800 HSC CART

From 8BitDev.org - Atari 7800 Development Wiki
Jump to: navigation, search
; ==============================================================================
; MODULE: HIGH_SCORE_CART.ASM
;
; Atari 7800 High Score Cartridge (HSC) Operating System ROM Disassembly
;
; V1.00 29/04/09 - Initial release by Mark Ball
; V1.11 05/12/14 - Further disassembly by Mike Saarna
; V1.12 10/04/25 - Further disassembly by Mike Saarna, addition of PAL hack
; V1.20 2026     - Architectural annotation, semantic clarification, and
;                  technical corrections (Score binary model, Watkins font mapping,
;                  15-bit packed initials bitfields, dual-page RAM specifications).
;
; Assembles with DASM:
;   DASM HIGH_SCORE_CART.ASM -F3 -OREBUILD_HSC.BIN
;
; ==============================================================================
; ARCHITECTURAL SPECIFICATION & MEMORY CONTRACT
; ==============================================================================
;
; 1. MEMORY MAP:
;    $1000 - $17FF : 2KB Non-Volatile Battery-Backed SRAM (NVRAM)
;    $3000 - $3FFF : 4KB HSC Operating System ROM
;
; 2. CALLING CONVENTION & DUAL-PAGE RAM CONTRACT (512 CONTIGUOUS BYTES):
;    When invoking any vector, the game MUST provide a page-aligned 512-byte
;    workspace spanning two contiguous 256-byte pages: Page N and Page N+1.
;      - Y Register = High byte of Argument Block (Page N). e.g., LDY #$25
;      - X Register = ignored. The low byte of the block is always $00.
;
;    Page N ($xx00 - $xxFF):
;      $00 - $11 : 18-Byte Caller Argument Block
;      $12       : HSCSTAT result (last byte of the BCD top score at $0F-$12)
;      $13 - $4F : 61-Byte Zero-Page Preservation Area (backs up ZP $80-$BC)
;      $50 - $FF : Text formatting and display workspace
;
;    Page N+1 ($xx00 + 1 page):
;      $00 - $86 : MARIA Display Lists (DL) generated dynamically by HSC
;      $87 - $FF : MARIA Display List List (DLL) (DPPL=$87, DPPH=Page N + 1)
;
; 3. DATA REPRESENTATIONS:
;    - Scores: The caller passes a 4-byte packed BCD score pointer. The HSC
;      converts this value into a 24-bit unsigned binary integer ($000000-$FFFFFF)
;      for ranking and Cart RAM storage. In Cart RAM, scores are stored as
;      3-byte LITTLE-ENDIAN binary integers (LSB, MID, MSB), NOT BCD.
;    - Initials: Three 5-bit characters packed into 2 consecutive bytes (15 bits).
;    - Font (Watkins Encoding):
;        $00-$19 : A - Z (0 - 25)
;        $1A     : '_' (Underscore, 26 - empty entry initials)
;        $1B     : '.' (Period, 27)
;        $1C     : '-' (Hyphen, 28)
;        $1D     : ' ' (Space, 29)
;        $1E     : ''' (Apostrophe, 30)
;        $1F     : Solid Block (31 - END in text entry)
;        $20     : '<-' (Left Arrow, 32 - BACKSPACE in text entry)
;        $21-$2A : '0' - '9' (Digits 0-9, 33 - 42)
;        $2B     : '?' (Question Mark, 43)
; ==============================================================================

    processor 6502

; ---------------------------------------------------------------
; Assembly-time configurables
; ---------------------------------------------------------------
; uncomment line to build the HSC PAL HACK v1
;PALHACK = 1

; ---------------------------------------------------------------
; MACROs.
; ---------------------------------------------------------------

; Watkins character encoding macro (ASCII character - 'A')
    mac WATKINS
        dc.b {1}-"A"
    endm

; ---------------------------------------------------------------
; TIA Hardware Registers
; ---------------------------------------------------------------
INPT4	    = $0C       ; Player 1 Fire Button Input (Bit 7: 0 = Pressed, 1 = Open)
INPT5	    = $0D       ; Player 2 Fire Button Input (Bit 7: 0 = Pressed, 1 = Open)
AUDF1	    = $18       ; Audio Frequency Channel 1
AUDV0	    = $19       ; Audio Volume Channel 0
AUDV1	    = $1A       ; Audio Volume Channel 1

; ---------------------------------------------------------------
; MARIA Graphics Registers
; ---------------------------------------------------------------
MSTAT	    = $28       ; MARIA Status Register (Bit 7: VBLANK status)
DPPH	    = $2C       ; Display List List Pointer High Byte
DPPL        = $30       ; Display List List Pointer Low Byte
CHARBASE    = $34       ; Character Base Address (Font bank pointer)
CTRL	    = $3C       ; MARIA Control Register ($4B = DMA On, $7F = DMA Off)

; ---------------------------------------------------------------
; Zero Page RAM Workspace ($80 - $BC)
; ---------------------------------------------------------------
; The HSC backs up these 61 bytes to (OrigArgRamLo) + $13..$4F on entry
; and restores them on exit, ensuring transparency to the caller.
ArgGameID1		= $80   ; Game ID Byte 1 (Bit 7 set = score passed /10, shown with trailing 0)
ArgGameID2		= $81   ; Game ID Byte 2
ArgGameDifficulty	= $82   ; Bits 7..2: Difficulty ID; Bits 1..0: Player Index
ArgGameController	= $83   ; Controller type ($00/$01 = Joystick, $02 = Custom Routine)
ArgGameNameLo		= $84   ; Pointer (Lo) to Game Title string (or custom controller driver)
ArgGameNameHi		= $85   ; Pointer (Hi) to Game Title string
ArgDifficultyNameLo	= $86   ; Pointer (Lo) to Difficulty text string
ArgDifficultyNameHi	= $87   ; Pointer (Hi) to Difficulty text string
ArgGameScoreLo		= $88   ; Pointer (Lo) to caller's 4-byte packed BCD score
ArgLogoDllLo		= $8A   ; Pointer (Lo) to Logo Display List List (DLL) (0 if text)
ArgLogoDllHi		= $8B   ; Pointer (Hi) to Logo Display List List (DLL)
ArgSoundRoutineLo	= $8C   ; Pointer (Lo) to caller sound update subroutine
ArgHSDisplayTime	= $8E   ; Display hold time in seconds (60 frames each), used by every vector
z8F	= $8F                   ; Initials char 0 / Indirect transfer pointer Lo
z90	= $90                   ; Initials char 1
z91	= $91                   ; Initials char 2
z92	= $92                   ; General 16-bit indirect scratch pointer (Lo)
z93	= $93                   ; General 16-bit indirect scratch pointer (Hi)
OrigArgRamLo	= $94           ; Caller Argument Block base pointer Lo (Always $00)
OrigArgRamHi	= $95           ; Caller Argument Block base pointer Hi (Page N)
z96	= $96                   ; Display List workspace base pointer Lo ($00)
z97	= $97                   ; Display List workspace base pointer Hi (Page N + 1)
z98	= $98                   ; Text line buffer pointer (Lo)
z99	= $99                   ; Text line buffer pointer (Hi)
z9A	= $9A                   ; DL cursor entry pointer 1 (Lo) - Character byte
z9B	= $9B                   ; DL cursor entry pointer 1 (Hi)
z9C	= $9C                   ; DL cursor entry pointer 2 (Lo) - Palette/Attribute byte
z9D	= $9D                   ; DL cursor entry pointer 2 (Hi)
z9E	= $9E                   ; DL cursor entry pointer 3 (Lo) - Horizontal X-position
z9F	= $9F                   ; DL cursor entry pointer 3 (Hi)
zA0	= $A0                   ; Owner name DL adjust pointer 1 (Lo)
zA1	= $A1                   ; Owner name DL adjust pointer 1 (Hi)
zA2	= $A2                   ; Owner name DL adjust pointer 2 (Lo)
zA3	= $A3                   ; Owner name DL adjust pointer 2 (Hi)
zA4	= $A4                   ; DLL generation write pointer Lo (Initialized to $87)
zA5	= $A5                   ; DLL generation write pointer Hi (Page N + 1)
GameScoreTableLo	= $A6   ; Cart RAM current 25-byte score block pointer (Lo)
GameScoreTableHi	= $A7   ; Cart RAM current 25-byte score block pointer (Hi)
zA8	= $A8                   ; Screen line height limit / countdown counter
PlayerController	= $A9   ; Player index, arg +$02 & 3 (0 = left port, 1-3 = right port)
zAA	= $AA                   ; Temporary loop counter / slot index
zAB	= $AB                   ; Temporary state / RAM initialization flag
zAC	= $AC                   ; Unpacked character temp / leading-zero suppression flag
zAD	= $AD                   ; 24-bit binary score - LSB (Bits 0..7)
zAE	= $AE                   ; 24-bit binary score - MID (Bits 8..15)
zAF	= $AF                   ; 24-bit binary score - MSB (Bits 16..23)
zB0	= $B0                   ; 24-bit binary math scratchpad - LSB
zB1	= $B1                   ; 24-bit binary math scratchpad - MID
zB2	= $B2                   ; 24-bit binary math scratchpad - MSB / DLL zone height total
zB3	= $B3                   ; Cursor blink countdown timer (Reloads to $1E = 30 frames)
zB4	= $B4                   ; Target score insertion offset in Cart RAM table
CurrentRankCounter	= $B5   ; Rank counter (counts 5 down; final rank 0 = 1st .. 4 = 5th)
zB6	= $B6                   ; Directory slot index ($00-$44): matched slot, or LRU victim
zB7	= $B7                   ; Input repeat throttle timer (Reloads to $0A = 10 frames)
zB8	= $B8                   ; RIOT console switch comparison mask
IdleTimerFine	= $B9   ; Screen saver idle timer, fine (Reloads to $E9)
                                ; (also the logo DLI flag while the DLL is built)
IdleTimerCoarse	= $BA   ; Screen saver idle timer, coarse (Reloads to $E9)
zBB	= $BB                   ; Y register preserve temporary storage
zSWCHBSave	= $BC           ; Stashed state of RIOT SWCHB console switches

; ---------------------------------------------------------------
; RIOT Hardware Registers (6532)
; ---------------------------------------------------------------
SWCHA	= $0280         ; Port A: Joysticks (P1: D7-D4, P2: D3-D0; 0=Active)
SWACNT	= $0281         ; Port A Direction Register
SWCHB	= $0282         ; Port B: Console Switches (Reset, Select, Pause, Difficulty)
SWBCNT	= $0283         ; Port B Direction Register

; ---------------------------------------------------------------
; Battery Backed NVRAM Memory Map (Size 2K: $1000 - $17FF)
; ---------------------------------------------------------------
CartRAMSaveZ94		= $1000 ; Preserved caller ZP $94 register
CartRAMSaveZ95		= $1001 ; Preserved caller ZP $95 register
CartRAMMagic1		= $1002 ; Magic Byte 1 ($68)
CartRAMMagic2		= $1003 ; Magic Byte 2 ($83)
CartRAMMagic3		= $1004 ; Magic Byte 3 ($AA)
CartRAMMagic4		= $1005 ; Magic Byte 4 ($55)
CartRAMMagic5		= $1006 ; Magic Byte 5 ($9C)
CartRAMEntryID		= $1007 ; Active Calling Vector ID
CartRAMOwnerName	= $1008 ; Cartridge Owner Personalization String (32 bytes)
CartRAMOwnerNameLength	= $1028 ; Owner string length (1 to 32 bytes)
CartRAMGameID1Table	= $1029 ; Directory: Game ID Byte 1 look-up table (69 entries)
CartRAMGameID2Table	= $106E ; Directory: Game ID Byte 2 look-up table (69 entries)
CartRAMGameDifficultyTable = $10B3 ; Directory: Difficulty table (69 entries, masked with $FC)
CartRAMGameAgeTable	= $10F8 ; Directory: LRU Age table (69 entries, 1 = MRU, $7F = Empty)
; Score Data Base Address: $113D = $113D + (SlotIndex * 25)
;   Total 69 slots * 25 bytes = 1725 bytes ($113D - $17F9).
;   Each 25-byte block stores 5 ranked score entries (5 bytes each).
;   Score Entry Format (5 Bytes):
;     Offset +0 : Packed Initials Byte 0: [C1.4 C1.3 C1.2 C0.4 C0.3 C0.2 C0.1 C0.0]
;     Offset +1 : Packed Initials Byte 1: [C1.1 C1.0   0  C2.4 C2.3 C2.2 C2.1 C2.0]
;     Offset +2 : Score LSB (24-bit unsigned binary integer, Little-Endian)
;     Offset +3 : Score MID
;     Offset +4 : Score MSB
CartRAMSavedEntry   	= $17FA ; Saved 5th place entry (5 bytes), restored on cancel
;			  $17FF ; Unused trailing byte

; ==============================================================================
; Function: HSCCPYFM (Vector @ $3FEE)
; Purpose:  Copies the active 25-byte high-score table from Cart NVRAM to the
;           user memory buffer pointed to by argument +$0F/$10 (copied to $8F).
; Inputs:   Y = Page number of Argument Block; ($8F) = Destination pointer
; Outputs:  A = $00, or $EF with nothing copied if the game has no table
; ==============================================================================
	org $3000,$FF

e3000:
	LDA #$EF                        ; Function ID: HSCCPYFM
	JSR HSCInitRoutine              ; Initialize context and locate score table
	LDY #$18                        ; 25 bytes total (offsets 24 down to 0)
e3007:
	LDA (GameScoreTableLo),Y        ; Read byte from Cart NVRAM
	STA (z8F),Y                     ; Write byte to caller's destination buffer
	DEY
	BPL e3007
	LDA #$00
	RTS

; ==============================================================================
; Function: HSCCPYTO (Vector @ $3FF1)
; Purpose:  Copies a 25-byte high-score table from the user memory buffer
;           pointed to by ($8F) directly into Cart NVRAM. The table is created
;           first if the game has none.
; Inputs:   Y = Page number of Argument Block; ($8F) = Source pointer
; Outputs:  A = $01
; ==============================================================================
e3011:
	LDA #$04                        ; Function ID: HSCCPYTO
	JSR HSCInitRoutine              ; Initialize context and locate score table
	LDY #$18                        ; 25 bytes total (offsets 24 down to 0)
e3018:
	LDA (z8F),Y                     ; Read byte from caller's source buffer
	STA (GameScoreTableLo),Y        ; Write byte to Cart NVRAM
	DEY
	BPL e3018
	LDA #$01
	RTS
	
; ==============================================================================
; Function: HSCSTAT (Vector @ $3FF7)
; Purpose:  Checks the status of the High Score Cartridge and evaluates whether
;           the caller's new score qualifies for ranking. Never creates a
;           table and never updates the LRU ages.
; Inputs:   Y = Page number of Argument Block
; Outputs:  (OrigArgRamLo)+$04 = Number of existing difficulty tables found
;           (OrigArgRamLo)+$05 = Rank (0 = 1st to 4 = 5th), or $FF if score does
;                                not rank ($FF for any score if no table exists)
;           (OrigArgRamLo)+$0F..$12 = Current 1st place score, packed BCD,
;                                big-endian, in the caller's units (0 if no table)
;           A = $00
; ==============================================================================
HSCSTAT:
	LDA #$02                        ; Function ID: HSCSTAT
	JSR HSCInitRoutine
	LDX #$44                        ; Scan all 69 directory slots ($44 down to $00)
	LDY #$00                        ; Match counter
e302B:
	LDA CartRAMGameID1Table,X
	CMP ArgGameID1
	BNE e3041
	LDA CartRAMGameID2Table,X
	CMP ArgGameID2
	BNE e3041
	LDA CartRAMGameDifficultyTable,X
	CMP #$7F                        ; Check if slot is unallocated (age not checked)
	BEQ e3041
	INY                             ; Increment matching difficulty counter
e3041:
	DEX
	BPL e302B
	TYA
	LDY #$04
	STA (OrigArgRamLo),Y            ; Store matching table count at Argument + $04
	LDA GameScoreTableHi
	BNE RankNewScore                ; If valid score table exists, evaluate rank
	; No valid score table exists (status check only on new table):
	LDY #$0F
	STA (OrigArgRamLo),Y            ; Clear status bytes $0F..$12
	INY
	STA (OrigArgRamLo),Y
	INY
	STA (OrigArgRamLo),Y
	INY
	STA (OrigArgRamLo),Y
	LDY #$05
	LDA #$FF                        ; Set rank = $FF (unranked)
	STA (OrigArgRamLo),Y
	RTS

; ==============================================================================
; Routine: RankNewScore
; Purpose: Evaluates candidate 24-bit binary score (in zAD, zAE, zAF) against
;          the 5 saved scores in the current NVRAM table.
;          The table entries are ranked in descending order:
;            Entry 0 (Offsets $00-$04) = 1st Place (Highest)
;            Entry 4 (Offsets $14-$18) = 5th Place (Lowest)
;          Evaluation starts at Entry 4 and proceeds upwards.
; Outputs: (OrigArgRamLo)+$05 contains rank (0-4) or $FF if not ranked.
; ==============================================================================
RankNewScore:
	JSR IsScoreNonZero              ; Check if candidate score is > 0
	BEQ RankNewScore_NotRanked      ; A score of 0 cannot rank
	LDY #$18                        ; Offset of 5th place entry MSB (Offset 24)
	LDA #$05                        ; Start rank evaluation at 5th place
	STA CurrentRankCounter
RankNewScore_CheckLoop:
	LDX #$02                        ; 3-byte comparison: X = 2 (MSB), 1 (MID), 0 (LSB)
RankNewScore_CompareByte:
	LDA (GameScoreTableLo),Y        ; Load byte from NVRAM table
	CMP zAD,X                       ; Compare TableByte with CandidateByte
	BCC RankNewScore_IsLowerOrEqual ; If Table < Candidate: Candidate is strictly higher!
	BNE RankNewScore_IsHigher       ; If Table > Candidate: Table is strictly higher!
	DEY                             ; Bytes are equal: advance to less significant byte
	DEX
	BPL RankNewScore_CompareByte    ; Continue comparing all 3 bytes
	BMI RankNewScore_IsLowerOrEqual ; Scores are identical: treat as qualifying rank
RankNewScore_IsHigher:
	; Table entry is strictly higher than candidate score: candidate cannot beat it.
	JSR AdvanceToNextScoreEntry
	TYA
	CLC
	ADC #$05
	CMP #$19
	BNE RankNewScore_StoreRank      ; Store current qualifying rank
	BEQ RankNewScore_NotRanked      ; Failed to beat 5th place: unranked
RankNewScore_IsLowerOrEqual:
	; Candidate score is greater than or equal to this table entry:
	JSR AdvanceToNextScoreEntry     ; Advance pointer to check next higher table entry
	DEY
	DEC CurrentRankCounter          ; Decrement rank counter (e.g., from 5th to 4th)
	BNE RankNewScore_CheckLoop      ; Loop to test against higher entry
RankNewScore_StoreRank:
	LDA CurrentRankCounter
	LDY #$05
	BNE RankNewScore_FinalizeStatus
RankNewScore_NotRanked:
	LDA #$FF                        ; $FF = Candidate score did not rank
	LDY #$05
RankNewScore_FinalizeStatus:
	STA (OrigArgRamLo),Y            ; Return rank result at Argument + $05
	JSR FormatAllScoresForDisplay   ; Render formatted table into display buffer
	LDX #$07
	LDY #$53
	BIT ArgGameID1
	BMI e30A9                       ; Custom alignment flag if Bit 7 of GameID1 is set
	INY
e30A9:
	; Pack the 8 digit characters of the formatted 1st place score back into
	; BCD (spaces count as 0) and return them at Argument + $0F..$12
	LDA (OrigArgRamLo),Y
	CMP #$1D                        ; Watkins Space ($1D)
	BNE e30B1
	LDA #$21                        ; Watkins Digit '0' ($21)
e30B1:
	SEC
	SBC #$21
	ASL
	ASL
	ASL
	ASL
	INY
	DEX
	PHA
	LDA (OrigArgRamLo),Y
	CMP #$1D
	BEQ e30C9
	PLA
	CLC
	ADC (OrigArgRamLo),Y
	SEC
	SBC #$21
	PHA
e30C9:
	INY
	DEX
	BPL e30A9
	LDY #$12
e30CF:
	PLA
	STA (OrigArgRamLo),Y            ; Store BCD top score to Argument + $0F..$12
	DEY
	CPY #$0F
	BPL e30CF
	LDA #$00
	RTS

; ==============================================================================
; Function: ShowHighScores / HSCATRCT (Vector @ $3FFA)
; Purpose:  Attract mode screen display. Builds the MARIA display lists in
;           Page N+1 and shows the table for the one game/difficulty given.
; Inputs:   Y = Page number of Argument Block
; Outputs:  A = $00 after the hold, or $FF at once (no hold, DMA left off) if
;           the game has no table
; ==============================================================================
ShowHighScores:
	LDA #$FF                        ; Function ID: ShowHighScores ($FF)
	JSR HSCInitRoutine
	JSR BuildDisplayLists           ; Construct MARIA display list structure
e30E2:
	DEC CartRAMEntryID
	DEC CartRAMEntryID              ; Entry ID -2: $FF->$FD, $00->$FE, $01->$FF
                                        ; (negative = attract layout)
e30E8:
	JSR FormatAllScoresForDisplay   ; Unpack initials and format decimal scores
	JSR e3FD8                       ; Reset DLL write pointers to Page N+1 : $87
e30EE:
	LDA ArgLogoDllHi
	BNE e30FC
	; Caller did not provide custom logo DLL: use the text game-name header
	LDA #<DllDisplayGameNameHeaderLut
	LDY #>DllDisplayGameNameHeaderLut
	JSR SetupDLL                    ; Build logo DLL in Page N+1
	JMP e3109
e30FC:
	LDA #$41
	LDY ArgDifficultyNameHi
	BNE e3104
	LDA #$4F
e3104:
	STA zA8
	JSR e36DE                       ; Decode caller's custom logo DLL
e3109:
	LDA ArgDifficultyNameHi
	BEQ e3114
	; Decode and display difficulty text header
	LDA #<DllDisplayGameDifficultyLut
	LDY #>DllDisplayGameDifficultyLut
	JSR SetupDLL
e3114:
	LDY #>DllDisplayFiveScoresLut
	LDA CartRAMEntryID
	BPL e311F                       ; Branch if qualifying score entry
	LDA #<DllDisplayFiveScoresLut   ; Attract layout: five-score table ($3D3D)
	BNE e3121
e311F:
	LDA #<e3D31                     ; Entry layout: CONGRATULATIONS/PLAYER n + table ($3D31)
e3121:
	JSR SetupDllPlusSome            ; Finalize DLL, enable MARIA DMA ($4B)
	LDA #$00
	RTS

; ==============================================================================
; Function: EnterHighScore_SuppliedInitials / HSCENTER2 (Vector @ $3FF4)
; Purpose:  Score entry with caller-supplied initials (argument +$0F..+$11,
;           copied to z8F, z90, z91). No interactive initials entry, but the
;           congratulations table is still shown for the hold time.
; ==============================================================================
EnterHighScore_SuppliedInitials:
	LDA #$01                        ; Function ID: HSCENTER2
	BNE EnterHighScore_Shared
	
; ==============================================================================
; Function: EnterHighScore / HSCENTER (Vector @ $3FFD)
; Purpose:  Interactive score entry. Evaluates candidate score rank, runs the
;           interactive initials entry screen, shifts existing scores down,
;           and commits the new score and initials to Cart NVRAM.
; ==============================================================================
EnterHighScore:
	LDA #$00                        ; Function ID: HSCENTER
EnterHighScore_Shared:
	JSR HSCInitRoutine
	JSR BuildDisplayLists
	JSR IsScoreNonZero
	BEQ e30E2                       ; If score is 0, skip entry and show table
	LDY #$13                        ; Back up lowest score (5th place, 5 bytes)
	LDX #$00
e313C:
	INY
	LDA (GameScoreTableLo),Y
	STA CartRAMSavedEntry,X          ; Stash 5th place entry into temporary buffer
	INX
	CPY #$18
	BNE e313C
	LDA #$05
	STA CurrentRankCounter          ; Start search at 5th place
e314B:
	LDX #$02                        ; Compare 3-byte binary score (MSB to LSB)
e314D:
	LDA (GameScoreTableLo),Y
	CMP zAD,X
	BCC EnterHighScore_CheckNextRank
	BNE EnterHighScore_RankFound
	DEY
	DEX
	BPL e314D
	BMI EnterHighScore_CheckNextRank
EnterHighScore_RankFound:
	JSR AdvanceToNextScoreEntry
	TYA
	CLC
	ADC #$05
	CMP #$19
	BNE e3173
	JMP e30E2                       ; Score does not rank; show table and exit
EnterHighScore_CheckNextRank:
	JSR AdvanceToNextScoreEntry
	DEY
	DEC CurrentRankCounter
	BNE e314B
	INY
	TYA
e3173:
	STA zB4                         ; zB4 = Target slot insertion byte offset
	LDY #$14
	LDX #$18
	STX zAA
EnterHighScore_ShiftScoresDown:
	; Shift all lower-ranking scores down by 5 bytes to open up the target slot
	CPY zB4
	BEQ EnterHighScore_InsertNewScore
	DEY
	TYA
	TAX
	LDA (GameScoreTableLo),Y
	LDY zAA
	STA (GameScoreTableLo),Y
	DEC zAA
	TXA
	TAY
	JMP EnterHighScore_ShiftScoresDown
EnterHighScore_InsertNewScore:
	; Insert the new 3-byte binary score into the opened slot
	LDY zAA
	LDX #$02
e3193:
	LDA zAD,X
	STA (GameScoreTableLo),Y
	DEY
	DEX
	BPL e3193
	JSR e30E8                       ; Format display buffer with updated table
	LDA CurrentRankCounter
	TAY
	TAX
	LDA #$F4
e31A4:
	CLC
	ADC #$0C
	DEY
	BPL e31A4
	CLC
	ADC #$50
	STA z98                         ; z98 = Text line buffer pointer
	LDA CartRAMEntryID
	CMP #$01                        ; HSCENTER2?
	BNE e31C8
	; HSCENTER2 path: Copy pre-provided initials directly from z8F-z91
	LDY #$00
	LDA z8F
	STA (z98),Y
	INY
	LDA z90
	STA (z98),Y
	INY
	LDA z91
	STA (z98),Y
	BPL EncodeAndSaveInitialsToTable ; Initial 3 with bit 7 set falls into
					 ; interactive entry below
e31C8:
	; Interactive path: Launch joystick initials entry screen
	LDA #$EC
e31CA:
	CLC
	ADC #$14
	DEX
	BPL e31CA
	CLC
	ADC #$31
	TAX
	JSR e3242                       ; Run interactive initials input loop

; ==============================================================================
; Routine: EncodeAndSaveInitialsToTable
; Purpose: Bit-packs three 5-bit Watkins initial characters into 2 bytes and
;          commits them to Cart NVRAM at the target score slot. Characters are
;          not masked to 5 bits. Also reached after a cancelled entry, where it
;          re-packs the restored entry's own initials.
; Bitfield Encoding:
;   Byte 0: [C1.4 C1.3 C1.2 C0.4 C0.3 C0.2 C0.1 C0.0]
;   Byte 1: [C1.1 C1.0   0  C2.4 C2.3 C2.2 C2.1 C2.0]
; ==============================================================================
EncodeAndSaveInitialsToTable:
	LDY #$01
	LDA (z98),Y                     ; Load Character 1
	AND #$1C                        ; Extract bits 4, 3, 2
	ASL
	ASL
	ASL                             ; Shift to bits 7, 6, 5
	DEY
	ORA (z98),Y                     ; Combine with Character 0 (bits 4..0)
	PHA                             ; Stash packed Byte 0
	INY
	LDA (z98),Y                     ; Load Character 1 again
	AND #$03                        ; Extract bits 1, 0
	CLC
	ROR
	ROR
	ROR                             ; Rotate bits 1, 0 into bits 7, 6
	INY
	ORA (z98),Y                     ; Combine with Character 2 (bits 4..0)
	LDY zB4
	INY
	STA (GameScoreTableLo),Y        ; Store packed Byte 1 to NVRAM (Offset +1)
	DEY
	PLA
	STA (GameScoreTableLo),Y        ; Store packed Byte 0 to NVRAM (Offset +0)
	LDA #$01
	RTS
	
;
; Sets cursor character attribute (OR with $80)
;
e31FC:
	LDA (z9C,X)
	ORA #$80
	STA (z9C,X)
	RTS
	
;
; Resets input repeat throttle and cursor blink timers
;
e3203:
	JSR e3AF9
	LDA #$0A                        ; Throttle repeat timer = 10 frames
	STA zB7
	LDA #$1E                        ; Cursor blink timer = 30 frames
	STA zB3
	RTS
	
;
; Configures display list address pointers for interactive character entry
;
e320F:
	JSR e3203
	LDY z97
	STX z9A
	STY z9B
	INX
	STX z9C
	STY z9D
	INX
	INX
	STX z9E
	STY z9F
	RTS
	
; ==============================================================================
; Routine: WaitVBlankAndTickIdleTimer
; Purpose: Waits for vertical blank and ticks the two-stage idle timer.
;          When it expires (233 x 233 frames), runs the screen saver.
; ==============================================================================
WaitVBlankAndTickIdleTimer:
	JSR WaitForVBlank
TickIdleTimer:
	DEC IdleTimerFine     ; Decrement inner loop counter
	BNE TickIdleTimer_Exit

	LDA #$E9                        ; Reload inner loop counter
	STA IdleTimerFine
	DEC IdleTimerCoarse    ; Decrement outer loop counter
	BNE TickIdleTimer_Exit

	STA IdleTimerCoarse
	JSR ScreenSaverWaitForInput     ; Blank the screen until any input
TickIdleTimer_Exit:
	RTS
	
;
; Stores current character and replaces buffer with temporary space ($1D)
;
e3239:
	LDA (z98),Y
	STA (z9A,X)
	LDA #$1D                        ; Watkins Space character
	STA (z98),Y
	RTS
	
; ==============================================================================
; Routine: e3242
; Purpose: Interactive Initials Entry Input Loop. UP/RIGHT = next character,
;          DOWN/LEFT = previous (A-Z, _ . - space ' END BACKSPACE; no digits),
;          FIRE (on release) to confirm. Position 3 offers only END/BACKSPACE.
;          RESET/SELECT finish at the current position; finishing at
;          position 0 cancels and restores the table.
; ==============================================================================
e3242:
	JSR e320F
	LDX #$00
	LDY #$00
	LDA #$1F                        ; Cursor DL palette/width byte: palette 0, width 1
	STA (z9C,X)
e324D:
	LDA #$00
	STA zAA                         ; Nothing changed yet at this position
	LDA #$1F                        ; Position 3 starts on END (solid block)
	CPY #$03
	BEQ e325C
	JSR e3239
	LDA (z9A,X)
e325C:
	STA (z9A,X)
e325E:
	STY zBB
	JSR e397C                       ; Read joystick input
	CMP #$F0                        ; Check if direction is centered
	BEQ e326A
	JSR e3AF9                       ; Reset throttle timer on movement
e326A:
	TYA
	BMI e3270                       ; Branch if fire button is NOT pressed
	JMP e3318                       ; Fire button pressed: confirm character
e3270:
	LDY zBB
	JSR WaitForVBlank
	LDA #$03
	AND SWCHB                       ; Check Reset / Select console switches
	CMP #$03
	BEQ e3281
	JMP e334E                       ; Console switch pressed: finish entry here
e3281:
	TYA
	PHA
	JSR e36DB                       ; Call caller's sound update callback
	PLA
	TAY
	LDX #$00
	JSR TickIdleTimer
	DEC zB7                         ; Decrement throttle timer
	BNE e3300
	LDA #$0A
	STA zB7
	JSR e3974
	AND #$90
	CMP #$90
	BNE e32CD
	JSR e3974
	AND #$60
	CMP #$60
	BEQ e3300
	; Joystick DOWN/LEFT: previous character
	LDA (z9A,X)
	CPY #$03
	BNE e32B1
	CMP #$1F
	BEQ e32C3
e32B1:
	SEC
	SBC #$01
	STA (z9A,X)
	PHP
	LDX #$01
	STX zAA
	LDX #$00
	PLP
	BPL e330E
	TYA
	BEQ e32C7
e32C3:
	LDA #$20
	BNE e32C9
e32C7:
	LDA #$1F
e32C9:
	STA (z9A,X)
	BNE e330E
e32CD:
	; Joystick UP/RIGHT: next character
	LDA (z9A,X)
	CMP #$1A                        ; '_' with nothing changed yet goes straight to 'A'
	BNE e32D9
	LDX zAA
	BNE e32D9
	LDA #$FF
e32D9:
	LDX #$01
	STA zAA
	LDX #$00
	CLC
	ADC #$01
	CMP #$21
	BEQ e32F1
	CMP #$20
	BNE e32FB
	TYA
	BEQ e32FB
	LDA #$20
	BNE e32FB
e32F1:
	CPY #$03
	BNE e32F9
	LDA #$1F
	BNE e32FB
e32F9:
	LDA #$00
e32FB:
	STA (z9A,X)
	JMP e330E
e3300:
	; Handle cursor blink
	DEC zB3
	BNE e3315
	LDX #$00
	LDA (z9C,X)
	EOR #$80                        ; Toggle cursor attribute bit 7
	STA (z9C,X)
	BNE e3311
e330E:
	JSR e31FC
e3311:
	LDA #$1E
	STA zB3
e3315:
	JMP e325E
e3318:
	; Fire button pressed: Handle character confirmation or backspace
	JSR e3AF0                       ; Wait for fire button release
	LDY zBB
	JSR WaitForVBlank
	JSR e31FC
	LDA (z9A,X)
	CMP #$20                        ; Left arrow (BACKSPACE)?
	BNE e333D
	CPY #$03
	BEQ e332F
	STA (z98),Y                     ; Arrow glyph stays in the position left behind
e332F:
	DEY                             ; Backspace: move left
	LDA (z9E,X)
	SEC
	SBC #$04                        ; Move cursor DL X-coordinate left
	STA (z9E,X)
	SEC
	BCC e333D
	JMP e324D
e333D:
	CMP #$1F                        ; Solid block (END)?
	BEQ e334E                       ; Terminate entry
	STA (z98),Y                     ; Store selected character into initials buffer
	INY                             ; Advance to next initial character
	LDA (z9E,X)
	CLC
	ADC #$04                        ; Move cursor DL X-coordinate right
	STA (z9E,X)
	JMP e324D
e334E:
	; Finish initials entry
	LDA #$00
	STA (z9C,X)                     ; Zero byte 1 ends the DL here: cursor removed
	TYA
	BNE e336E
	; Initials entry was cancelled on first character: revert score table insertion
	LDA zB4
	STA zAA
	CLC
	ADC #$05
	TAX
e335D:
	TAY
	CPY #$19
	BEQ e337A
	LDA (GameScoreTableLo),Y
	LDY zAA
	INC zAA
	STA (GameScoreTableLo),Y
	INX
	TXA
	BNE e335D
e336E:
	; Pad remaining initial slots with spaces ($1D)
	CPY #$03
	BPL e3379
	LDA #$1D
	STA (z98),Y
	INY
	BNE e336E
e3379:
	RTS
	
;
; Restores backed-up 5th place entry from CartRAMSavedEntry if entry cancelled.
; A table created by this call stays in the directory.
;
e337A:
	LDX #$04
e337C:
	DEY
	LDA CartRAMSavedEntry,X
	STA (GameScoreTableLo),Y
	DEX
	BPL e337C
	JSR FormatAllScoresForDisplay
	RTS
	
; ==============================================================================
; Routine: e3389
; Purpose: Interactive Owner Name entry loop for cartridge personalization.
;          Up to 32 characters (limit in zA8); END finishes and is stored after
;          the last character. No console abort and no sound callback.
; ==============================================================================
e3389:
	JSR e320F
	LDA z97
	LDX #$18
	STX zA0
	STA zA1
	INX
	STX zA2
	STA zA3
	LDX #$00
	TXA
	TAY
	STA (z98),Y
	LDA #$1F
	STA (z9C,X)
e33A3:
	JSR e3239
e33A6:
	STY zBB
	JSR e397C
	CMP #$F0
	BEQ e33B2
	JSR e3AF9
e33B2:
	TYA
	BMI e33B8
	JMP e3420
e33B8:
	LDY zBB
	JSR WaitVBlankAndTickIdleTimer
	DEC zB7
	BNE e3408
	LDA #$0A
	STA zB7
	JSR e3974
	AND #$90
	CMP #$90
	BNE e33ED
	JSR e3974
	AND #$60
	CMP #$60
	BEQ e3408
	LDA (z9A,X)
	SEC
	SBC #$01
	STA (z9A,X)
	BPL e3416
	TYA
	BEQ e33E7
	LDA #$20
	BNE e33E9
e33E7:
	LDA #$1F
e33E9:
	STA (z9A,X)
	BNE e3416
e33ED:
	LDA (z9A,X)
	CLC
	ADC #$01
	CMP #$21
	BEQ e3401
	CMP #$20
	BNE e3403
	TYA
	BEQ e3403
	LDA #$20
	BNE e3403
e3401:
	LDA #$00
e3403:
	STA (z9A,X)
	JMP e3416
e3408:
	DEC zB3
	BNE e341D
	LDX #$00
	LDA (z9C,X)
	EOR #$80
	STA (z9C,X)
	BNE e3419
e3416:
	JSR e31FC
e3419:
	LDA #$1E
	STA zB3
e341D:
	JMP e33A6
e3420:
	JSR e3AF0
	LDY zBB
	JSR WaitForVBlank
	JSR e31FC
	LDA (z9A,X)
	CMP #$20
	BNE e344C
	STA (z98),Y
	DEY
	LDA (z9E,X)
	SEC
	SBC #$02
	STA (z9E,X)
	LDA (zA0,X)
	CLC
	ADC #$01
	STA (zA0,X)
	LDA (zA2,X)
	CLC
	ADC #$02
	STA (zA2,X)
	JMP e33A3
e344C:
	STA (z98),Y
	CMP #$1F
	BEQ e3471
	INY
	CPY zA8
	BEQ e3471
	STA (z98),Y
	LDA (z9E,X)
	CLC
	ADC #$02
	STA (z9E,X)
	LDA (zA0,X)
	SEC
	SBC #$01
	STA (zA0,X)
	LDA (zA2,X)
	SEC
	SBC #$02
	STA (zA2,X)
	JMP e33A3
e3471:
	LDA #$00
	STA (z9C,X)
	STY CartRAMOwnerNameLength
	TYA
	BNE e3482
	LDA #$1D
	STA (z98),Y
	INC CartRAMOwnerNameLength
e3482:
	RTS

; ==============================================================================
; Routine: HSCInitRoutine
; Purpose: Master context initialization for the High Score Cartridge.
;          1. Stashes caller's ZP $94/$95 into NVRAM ($1000-$1001).
;          2. Preserves caller's ZP $80-$BC to UserRAM + $13..$4F (61 bytes).
;          3. Copies caller's Argument Block ($00-$11) into ZP $80-$91.
;          4. Verifies Cart NVRAM magic bytes ($68,$83,$AA,$55,$9C). If corrupt,
;             initializes directory structures and owner personalization.
;          5. Searches 69-slot directory for Game ID + Difficulty match.
;          6. Implements LRU aging policy and allocates score table pointer.
; Inputs:  A = Calling Function ID
;          Y = Page number of Caller Argument Block (Page N)
; ==============================================================================
HSCInitRoutine:
	STA CartRAMEntryID              ; Store entry ID for routing
	SEI                             ; Disable interrupts
	CLD                             ; Clear decimal mode
	CMP #$02                        ; HSCSTAT
	BEQ HSCInit_SkipDmaOff
	CMP #$EF                        ; HSCCPYFM
	BEQ HSCInit_SkipDmaOff
	CMP #$04                        ; HSCCPYTO
	BEQ HSCInit_SkipDmaOff
	
	LDA #$7F		        ; Disable MARIA DMA
	STA CTRL
	LDA #$00		        ; Mute TIA audio channels
	STA AUDV0
	STA AUDV1
HSCInit_SkipDmaOff:
	LDA $94
	STA CartRAMSaveZ94              ; Preserve caller ZP $94
	LDA $95
	STA CartRAMSaveZ95              ; Preserve caller ZP $95
	LDX #$00		        ; Low byte is strictly $00
	STX OrigArgRamLo                ; OrigArgRamLo = $94
	STY OrigArgRamHi	        ; OrigArgRamHi = $95 (Page N)
	LDY #$13
	LDX #$3C
HSCInit_BackupZpToUserRam:
	; Back up ZP $80-$BC (61 bytes) into (OrigArgRamLo) + $13..$4F
	LDA ArgGameID1,X
	STA (OrigArgRamLo),Y
	INY
	DEX
	BPL HSCInit_BackupZpToUserRam

	LDA OrigArgRamLo
	LDY OrigArgRamHi
	STA z96
	INY
	STY z97			        ; z96/z97 points to Page N + 1 (DL Workspace)
	LDA SWCHB		        ; Read RIOT console switches
	STA zSWCHBSave		        ; Stash for restoration on exit
	LDA #$FF
	STA SWCHB		        ; Set Port B pins high
	LDY #$11
HSCInit_CopyArgsToZp:
	; Copy 18-byte Argument Block into zero-page $80-$91
	LDA (OrigArgRamLo),Y
	STA ArgGameID1,Y
	DEY
	BPL HSCInit_CopyArgsToZp

	LDA ArgGameDifficulty
	AND #$03
	STA PlayerController            ; Extract player index (bits 1..0), before the
	                                ; default set below can replace the arguments
	LDA ArgLogoDllHi
	ORA ArgGameNameHi
	BNE e34EE                       ; Skip if custom name or logo provided
	LDY #$0E
HSCInit_SetDefaultArgs:
	; Load default argument set (bytes $00-$0E) if no name or logo provided
	LDA e39EF,Y
	STA ArgGameID1,Y
	DEY
	BPL HSCInit_SetDefaultArgs
e34EE:
	; Stack manipulation: Pushes $3C, $2B beneath the JSR return address,
	; so the vector body's final RTS continues at $3C2C (display hold).
	PLA
	TAX
	PLA
	TAY
	LDA #$3C
	PHA
	LDA #$2B
	PHA
	TYA
	PHA
	TXA
	PHA
	LDA CartRAMEntryID
	CMP #$02                        ; HSCSTAT
	BEQ e350E
	CMP #$EF                        ; HSCCPYFM
	BEQ e350E
	CMP #$04                        ; HSCCPYTO
	BEQ e350E
	JSR HSCSETRS2                   ; Set font palette and CHARBASE
e350E:
	LDA #$00
	STA zAB
HSCInit_CheckRamMagic:
	; Check NVRAM initialization signature ($68, $83, $AA, $55, $9C)
	LDA #$68
	CMP CartRAMMagic1
	BNE HSCInit_RamIsUninitialized
	LDA #$83
	CMP CartRAMMagic2
	BNE HSCInit_RamIsUninitialized
	LDA #$AA
	CMP CartRAMMagic3
	BNE HSCInit_RamIsUninitialized
	LDA #$55
	CMP CartRAMMagic4
	BNE HSCInit_RamIsUninitialized
	LDA #$9C
	CMP CartRAMMagic5
	BEQ HSCInit_RamInitialized
HSCInit_RamIsUninitialized:
	LDA zAB
	INC zAB
	TAX
	BEQ HSCInit_CheckRamMagic
	; NVRAM signature missing: format directory tables
	LDA #$68
	STA CartRAMMagic1
	LDA #$83
	STA CartRAMMagic2
	LDA #$AA
	STA CartRAMMagic3
	LDA #$55
	STA CartRAMMagic4
	LDA #$9C
	STA CartRAMMagic5
	JSR e3FCF                       ; Initialize all 69 directory slots to $7F
	LDA CartRAMEntryID
	CMP #$EF                        ; HSCCPYFM
	BEQ HSCInit_RamInitialized
	CMP #$04                        ; HSCCPYTO
	BEQ HSCInit_RamInitialized
	JSR e3C95                       ; Launch cart personalization setup
					; (leaves PlayerController = 0 for the rest of the call)
	JSR DisableMariaDMA
HSCInit_RamInitialized:
	JSR e3A81
	LDA #$00
	STA zAA
	; Search backwards through 69 directory slots ($44 down to $00)
	LDX #$45
HSCInit_FindGameInTable:
	DEX
	BMI HSCInit_CheckIfNewGameEntryNeeded ; Not found: allocate new slot
	LDA zAA
	CMP CartRAMGameAgeTable,X
	BPL e3583
	LDA CartRAMGameAgeTable,X
	STA zAA
	STX zB6                         ; zB6 = Oldest LRU slot index candidate
					; (left unset if no slot has an age above 0)
e3583:
	LDA ArgGameID1
	CMP CartRAMGameID1Table,X
	BNE HSCInit_FindGameInTable
	LDA ArgGameID2
	CMP CartRAMGameID2Table,X
	BNE HSCInit_FindGameInTable
	LDA ArgGameDifficulty
	AND #$FC
	CMP CartRAMGameDifficultyTable,X
	BNE HSCInit_FindGameInTable
	; Matching Game ID and Difficulty found!
	STX zB6
	LDA CartRAMGameAgeTable,X
	STA zAA
HSCInit_SetupScoreTablePointers:
	TXA
	PHA
	LDA CartRAMEntryID
	CMP #$02                        ; HSCSTAT
	BEQ e35BE
	; Update LRU Aging Indices: active slot is set to 0, then aged along with
	; the others below, so it ends at 1 (Most Recently Used)
	LDA #$00
	STA CartRAMGameAgeTable,X
	LDX #$44
HSCInit_UpdateGameAges:
	LDA CartRAMGameAgeTable,X
	CMP zAA
	BPL e35BB
	INC CartRAMGameAgeTable,X     ; Age every slot younger than the old age
e35BB:
	DEX
	BPL HSCInit_UpdateGameAges
e35BE:
	; Calculate NVRAM score block address: TableAddress = $113D + (SlotIndex * 25)
	PLA
	LDY #$00
	JSR Setupz92WithAY
	ASL
	JSR e3A2C
	JSR Addz92ToAYStoreInz92
	JSR e3A2C
	JSR e3A2C
	JSR Addz92ToAYStoreInz92
	LDA #$3D
	LDY #$11
	JSR Addz92ToAYStoreInAY         ; Add base $113D
	STA GameScoreTableLo            ; GameScoreTableLo/Hi = 25-byte score block
	STY GameScoreTableHi
	RTS
	
;
; Allocates new slot if game entry not found
;
HSCInit_CheckIfNewGameEntryNeeded:
	LDY CartRAMEntryID
	BPL HSCInit_HandleNewGameEntry
	; Negative entry ID (HSCATRCT $FF, HSCCPYFM $EF): no table. A negative
	; hold time skips the hold; dropping the return address skips the vector
	; body, so RTS goes straight to $3C2C and the normal restore/exit.
	STY ArgHSDisplayTime
	PLA
	PLA
	TYA
	RTS
	
HSCInit_HandleNewGameEntry:
	CPY #$02                        ; Status check only?
	BNE HSCInit_CreateNewGameEntry
	LDA #$00
	STA GameScoreTableHi            ; Flag to HSCSTAT that table does not exist
	RTS

HSCInit_CreateNewGameEntry:
	; Allocate oldest LRU slot index (in zB6) to new game
	LDX zB6
	LDA ArgGameID1
	STA CartRAMGameID1Table,X
	LDA ArgGameID2
	STA CartRAMGameID2Table,X
	LDA ArgGameDifficulty
	AND #$FC
	STA CartRAMGameDifficultyTable,X
	JSR HSCInit_SetupScoreTablePointers
	LDY #$18                        ; Initialize 5 entries with default score 0
HSCInit_InitNewScoreTableLoop:
	LDX #$02
	LDA #$00
HSCInit_ClearScoreBytes:
	STA (GameScoreTableLo),Y        ; Clear score bytes (Offset +2, +3, +4)
	DEY
	DEX
	BPL HSCInit_ClearScoreBytes
HSCInit_SetDefaultInitials:
	; Set default initials to "___" (Underscore x3: packed as $DA, $9A)
	LDA #$9A                        ; Packed Initials Byte 1
	STA (GameScoreTableLo),Y
	DEY
	LDA #$DA                        ; Packed Initials Byte 0
	STA (GameScoreTableLo),Y
	DEY
	BPL HSCInit_InitNewScoreTableLoop
	RTS
	
; ==============================================================================
; Routine: BuildDisplayLists
; Purpose: Constructs the MARIA Display Lists (DL) and Display List Lists (DLL)
;          in Page N+1 to render high-score tables and headers.
; ==============================================================================
BuildDisplayLists:
	LDY #$86
e3625:
	LDX #$13
e3627:
	LDA e3E4F,X
	STA (z96),Y
	DEY
	DEX
	BPL e3627
	TYA
	BPL e3625
	LDY #$22
e3635:
	LDA e3E2C,Y
	CMP #$FD
	BNE e363E
	LDA OrigArgRamHi
e363E:
	STA (z96),Y
	DEY
	BPL e3635
	LDY #$1F
	SEC
	LDA e3E4B
	SBC CartRAMOwnerNameLength
	STA (z96),Y
	INY
	SEC
	LDA e3E4C
	SBC CartRAMOwnerNameLength
	SEC
	SBC CartRAMOwnerNameLength
	STA (z96),Y
	LDA #$01
	LDY #$23
	JSR e3DE7
	LDA OrigArgRamHi
	LDY #$29
	STA (z96),Y
	LDA #$00
	JSR e3DE7
	LDY #$2E
	STA (z96),Y
	JSR e3DE9
	LDA #$0C
	LDY #$27
	JSR e3DE7
	LDY #$2C
	JSR e3DE9
	LDY #$24
	LDA #$9F
	STA (z96),Y
	LDY #$2A
	LDA #$97
	STA (z96),Y
	LDY #$2F
	LDA #$9D
	STA (z96),Y
	LDA #$8C
	LDY OrigArgRamHi
	STY z99
	JSR Setupz92WithAY
	LDY #$19
e369E:
	LDA (ArgGameNameLo),Y
	STA (z92),Y
	DEY
	BPL e369E
	LDA #$A6
	STA z92
	LDY #$19
e36AB:
	LDA (ArgDifficultyNameLo),Y
	STA (z92),Y
	DEY
	BPL e36AB
	LDA #$C0
	STA z92
	LDY #$18
	LDA e3F2C,Y
	CLC
	ADC PlayerController
	STA (z92),Y
	DEY
e36C1:
	LDA e3F2C,Y
	STA (z92),Y
	DEY
	BPL e36C1
	LDA #$DA
	LDY OrigArgRamHi
	JSR Setupz92WithAY
	LDY #$1F
e36D2:
	LDA CartRAMOwnerName,Y
	STA (z92),Y
	DEY
	BPL e36D2
	RTS
	
; ==============================================================================
; Routine: e36DB
; Purpose: Calls the caller's sound update callback subroutine.
; ==============================================================================
e36DB:
	JMP (ArgSoundRoutineLo)

;
; Game Logo DLL Decompressor: copies the caller's 3-byte DLL entries verbatim
; until the zone height total reaches zA8 ($41 or $4F lines). A DLI on the
; first entry sets the DLI flag ($40) for SetupDLL.
;
e36DE:
	LDA #$00
	TAY
	TAX
e36E2:
	LDA (ArgLogoDllLo),Y
	BPL e36F0
	CPY #$00
	BNE e36F0
	LDA #$40
	STA IdleTimerFine
	LDA (ArgLogoDllLo),Y
e36F0:
	STA (zA4,X)
	PHA
	INY
	INC zA4
	LDA (ArgLogoDllLo),Y
	STA (zA4,X)
	INY
	INC zA4
	LDA (ArgLogoDllLo),Y
	STA (zA4,X)
	PLA
	JSR UpdateDllDecodeState
	CMP zA8
	BMI e36E2
	RTS
	
; ==============================================================================
; Routine: UpdateDllDecodeState
; Purpose: Advances DLL generation write pointer and adds the zone height
;          ((byte0 & $0F) + 1) to the running line total in zB2.
; ==============================================================================
UpdateDllDecodeState:
	INY
	INC zA4
	SEC
	AND #$0F
	ADC zB2
	STA zB2
UpdateDllDecodeState_Exit:
	RTS
	
; ==============================================================================
; Routine: SetupDLL / SetupDllPlusSome
; Purpose: Decompresses encoded DLL byte stream into Page N+1 : $87 and enables
;          MARIA DMA ($4B).
; ==============================================================================
SetupDLL:            ; $3715
	JSR Setupz92WithAY
	LDY #$00
	LDX #$00
e371C:
	LDA (z92),Y                     ; Read encoded DLL byte
	BEQ UpdateDllDecodeState_Exit   ; 0 = End of DLL stream
	BPL e3728
	ASL IdleTimerFine     ; DLI kept only on the first flagged entry
	BNE e3728                       ; after a logo whose first entry had a DLI
	AND #$7F
e3728:
	STA (zA4,X)                     ; Store DLL entry byte 1
	JSR UpdateDllDecodeState
	LDA z97
	STA (zA4,X)                     ; Store DLL entry byte 2 (Page N + 1)
	INC zA4
	INY
	LDA (z92),Y
	STA (zA4,X)                     ; Store DLL entry byte 3
	INC zA4
	INY
	BNE e371C
SetupDllPlusSome:
	JSR SetupDLL
	LDA #$F2
	SEC
	SBC zB2
	PHP
	LDA #$61
	LDY #$3D
	PLP
	BPL SetupDllPlusSome
	JSR WaitForVBlank
	LDA z97
	STA DPPH                        ; DPPH = Page N + 1
	LDA #$87
	STA DPPL                        ; DPPL = $87
	LDA #$4B                        ; Enable MARIA DMA
	STA CTRL
	RTS

; ---------------------------------------------------------------
; Wait for 2 Vertical Blanks
; ---------------------------------------------------------------
WaitFor2VBlanks:
	JSR WaitForVBlank

; Fall through...

; ---------------------------------------------------------------
; Wait for Vertical Blank: waits for the start of the next VBLANK
; (MSTAT bit 7 rising edge; if already in VBLANK, waits for the next)
; ---------------------------------------------------------------
WaitForVBlank:
WaitForVBlankOffLoop:
	BIT MSTAT
	BMI WaitForVBlankOffLoop
WaitForVBlankOnLoop:
	BIT MSTAT
	BPL WaitForVBlankOnLoop
	RTS

; ==============================================================================
; Routine: FormatAllScoresForDisplay
; Purpose: Reads all 5 high-score entries from Cart NVRAM. For each entry:
;          1. Decodes 15-bit packed initials into 3 characters.
;          2. Converts 24-bit binary score into decimal characters with
;             leading-zero suppression.
;          3. Writes formatted text into the display workspace.
; ==============================================================================
FormatAllScoresForDisplay:
	LDY #$00
	LDA #$50
	STA z92
	LDA OrigArgRamHi
	STA z93                         ; z92/z93 points to Page N : $50
e3773:
	LDA (GameScoreTableLo),Y        ; Load Packed Initials Byte 0
	INY
	PHA
	LDA (GameScoreTableLo),Y        ; Load Packed Initials Byte 1
	INY
	STY zAA
	TAX
	PLA
	JSR e3AD6                       ; Unpack initials: A=C0, X=C1, Y=C2
	STY zAC
	LDY #$00
	STA (z92),Y                     ; Store Initial Character 0
	INY
	TXA
	STA (z92),Y                     ; Store Initial Character 1
	INY
	LDA zAC
	STA (z92),Y                     ; Store Initial Character 2
	INY
	LDA #$03
	LDY #$00
	JSR Addz92ToAYStoreInz92
	LDY zAA
	LDA (GameScoreTableLo),Y        ; Load 24-bit binary score LSB
	STA zAD
	INY
	LDA (GameScoreTableLo),Y        ; Load 24-bit binary score MID
	STA zAE
	INY
	LDA (GameScoreTableLo),Y        ; Load 24-bit binary score MSB
	STA zAF
	INY
	STY zAA
	JSR e37BC                       ; Convert 24-bit binary to decimal characters
	LDA #$09
	LDY #$00
	JSR Addz92ToAYStoreInz92
	LDY zAA
	CPY #$19
	BNE e3773                       ; Loop for all 5 score entries
	RTS
	
; ==============================================================================
; Routine: e37BC
; Purpose: Converts 24-bit unsigned binary integer in zAD, zAE, zAF into decimal
;          digits using successive subtractions against table e3EC7 (powers of 10).
;          Replaces leading zeros with Watkins space ($1D).
; ==============================================================================
e37BC:
	LDY #$00
	STY zAB
	STY zAC                         ; zAC = Leading zero suppression flag
	LDA #$FD
	PHA
	LDA ArgGameID1
	BPL e381B
	PLA
e37CA:
	LDA #$FF
	STA zB1
e37CE:
	INC zB1
	LDA zAD
	SEC
	SBC e3EC7,Y                     ; Subtract power of 10 LSB
	STA zAD
	LDA zAE
	SBC e3EC8,Y                     ; Subtract power of 10 MID
	STA zAE
	LDA zAF
	SBC e3EC9,Y                     ; Subtract power of 10 MSB
	STA zAF
	BCS e37CE                       ; Loop until borrow occurs
	; Add back power of 10 to restore remainder:
	LDA zAD
	CLC
	ADC e3EC7,Y
	STA zAD
	LDA zAE
	ADC e3EC8,Y
	STA zAE
	LDA zAF
	ADC e3EC9,Y
	STA zAF
	TYA
	PHA
	LDA zB1
	LDY zAB
	CLC
	ADC #$21                        ; Convert remainder count to Watkins digit '0'-'9'
	CPY #$08
	BNE e380F
	CMP #$21
	BEQ e3821
e380F:
	CMP #$21                        ; Is digit '0'?
	BEQ e381B
	PHA
	LDA #$80                        ; Set flag: non-zero digit found (disable zero suppression)
	STA zAC
	PLA
	BNE e3821
e381B:
	BIT zAC                         ; Check if leading zero suppression active
	BMI e3821
	LDA #$1D                        ; Replace leading zero with Watkins space ($1D)
e3821:
	STA (z92),Y                     ; Write digit character to buffer
	INY
	STY zAB
	PLA
	TAY
	INY
	INY
	INY
	CPY #$18
	BNE e37CA                       ; Loop for all powers of 10
	LDY zAB
	CPY #$08
	BNE e3839
	LDA #$21
	STA (z92),Y
e3839:
	RTS
	
; ==============================================================================
; Routine: e383A
; Purpose: Cart Personalization and Clear High Scores Menu Navigation Logic.
; ==============================================================================
e383A:
	JSR e3AF9
	LDY #$03
	JSR e3EF9
	JSR e3EE8
	LDY #$06
	JSR e3EF9
	JSR e3EE7
	PLA
	AND #$10
	BNE e386C
	LDA #$4B
	LDX #$2A
	CPX z92
	BEQ e385F
	LDA z92
	SEC
	SBC #$0B
e385F:
	STA z92
	JSR e3EDF
	LDY #$04
	JSR e3EDF
	JMP e3BCC
e386C:
	LDA #$2A
	LDX #$4B
	CPX z92
	BEQ e385F
	LDA z92
	CLC
	ADC #$0B
	BNE e385F
e387B:
	LDA z92
	STA zAD
	LDY #$03
	JSR e3EF0
	LDY #$06
	JSR e3EF0
	LDY #$00
	LDA #$56
	STA (z9A),Y
	JSR e3203
	LDA #$60
	STA z92
	JSR e3AF0
e3899:
	JSR WaitVBlankAndTickIdleTimer
	DEC zB7
	BNE e38B0
	LDA #$0A
	STA zB7
	JSR e397C
	AND #$C0
	CMP #$C0
	BNE e38C0
	TYA
	BPL e38DE
e38B0:
	DEC zB3
	BNE e3899
	LDA #$1E
	STA zB3
	LDY #$03
	JSR e3EF3
	JMP e3899
e38C0:
	JSR e3AF9
	LDY #$03
	JSR e3EF9
	LDA #$60
	CMP z92
	BNE e38D0
	LDA #$5B
e38D0:
	STA z92
	JSR e3EF0
e38D5:
	JSR e397C
	CMP #$F0
	BNE e38D5
	BEQ e3899
e38DE:
	LDA #$05
	LDY #$00
	STA (z9A),Y
	JSR e3AF0
	LDA z92
	LDY zAD
	STY z92
	CMP #$60
	BEQ e38F4
	JMP e39C8
e38F4:
	JMP e3BEB
	
; ---------------------------------------------------------------
; Font Bitmap Data - Scanline Row 6 ($3900 - $392B)
; Contains detection signature bytes at $3900 ($C6) and $3904 ($FE)
; ---------------------------------------------------------------
	org $3900,$FF
FontDataStartAddr:
FontDataRow6:
	dc.b $c6		; HSC Signature Byte 1 ($3900)
	dc.b $fc
	dc.b $70
	dc.b $f8
	dc.b $fe		; HSC Signature Byte 2 ($3904)
	dc.b $c0
	dc.b $3e,$c6,$7e,$38,$ce,$fe
	dc.b $c6,$c6,$38,$c0,$36,$ce
	dc.b $7c,$18,$7c,$10,$6c,$c6
	dc.b $30,$fe,$ff,$38,$00,$00
	dc.b $00,$ff,$00,$38,$7e,$fe
	dc.b $7c,$0c,$78,$38,$30,$38
	dc.b $78,$30

; ==============================================================================
; Routine: ScreenSaverWaitForInput
; Purpose: Disables MARIA DMA and loops polling joysticks and console switches
;          until user activity is detected. Re-enables DMA on exit.
; ==============================================================================
ScreenSaverWaitForInput:
        JSR DisableMariaDMA             ; Disable DMA before polling RIOT
	LDA #$06
	STA SWBCNT
	LDA #$00
	STA SWCHB
	LDA #$FF
	STA SWACNT
	LDA #$11
	STA SWCHA
	LDA #$06
	STA SWCHB
	LDA #$00
	STA SWACNT
ScreenSaverWaitForInput_SetPortBDir:
	STA SWBCNT
ScreenSaverWaitForInput_Loop:
	LDA SWCHA                       ; Check joysticks
	CMP #$FF
	BNE EnableMariaDMA
	LDA INPT4                       ; Check Player 1 fire button
	BPL EnableMariaDMA
	LDA INPT5                       ; Check Player 2 fire button
	BPL EnableMariaDMA
	LDA SWCHB                       ; Check console switches (Reset/Select/Pause)
ScreenSaverWaitForInput_CheckSwitches:
	AND #$0B
	CMP #$0B
	BEQ ScreenSaverWaitForInput_Loop
EnableMariaDMA:
	LDA #$4B                        ; Enable MARIA DMA ($4B)
_SetMariaCtrlAndWaitForVBlank:
	JSR WaitForVBlank
	STA CTRL
	RTS

; ------------------------------------------------------------------------------
; Routine: DisableMariaDMA
; Purpose: Disables MARIA DMA ($7F) cleanly at vertical blank.
; ------------------------------------------------------------------------------
DisableMariaDMA:
	LDA #$7F
	BNE _SetMariaCtrlAndWaitForVBlank
e3974:
	STY zBB
	JSR e397C
	LDY zBB
	RTS
	
; ==============================================================================
; Routine: e397C
; Purpose: Reads controller input based on ArgGameController configuration:
;          $00/$01: Standard digital joystick (SWCHA, INPT4, INPT5)
;          $02    : Custom controller driver callback at (ArgGameNameLo)
; ==============================================================================
e397C:
	LDA ArgGameController
	CMP #$02
	BEQ e399C
	LDA PlayerController
	BNE e3990
	LDA SWCHA
	AND #$F0                        ; Player 1 joystick directions
	LDY INPT4                       ; Player 1 fire button
	JMP e39A5
e3990:
	LDA SWCHA
	ASL
	ASL
	ASL
	ASL                             ; Player 2 joystick directions
	LDY INPT5                       ; Player 2 fire button
	JMP e39A5
e399C:
	LDX PlayerController
	JSR e39A6                       ; Call custom controller routine
	AND #$F0
	LDX #$00
e39A5:
	RTS

;
; Indirect jump to caller's custom controller driver
;
e39A6:
	JMP (ArgGameNameLo) 

; ==============================================================================
; Function: HSCSETRS (Vector @ $3FEB)
; Purpose:  Sets MARIA control, character base font bank, and palette registers
;           (P0C2 - P4C2) to standard HSC configuration.
; ==============================================================================
HSCSETRS:
	LDA #$4B	                ; DMA on
	STA CTRL
HSCSETRS2:
	LDX #$E0
	LDA #$00
	STA $40,X
	INX
	INX
	LDY #$04
e39B7:
	LDA e3D2C,Y
	STA $40,X
	INX
	INX
	INX
	INX
	DEY
	BPL e39B7
	LDA #>FontDataStartAddr         ; Point CHARBASE to $3900 font bank
	STA CHARBASE
	RTS
	
; ==============================================================================
; Routine: e39C8
; Purpose: Dispatches menu actions: Clear Game, Clear All, Personalize, Exit.
; ==============================================================================
e39C8:
	LDA #$2A
	CMP z92
	BNE e39D1
	JSR e3FC4                       ; Clear this game's high scores
e39D1:
	LDA #$35
	CMP z92
	BNE e39DA
	JSR e3FCF                       ; Clear all games from NVRAM
e39DA:
	LDA #$40
	CMP z92
	BNE e39E6
	JSR e3C95                       ; Personalize cart
e39E3:
	JMP e3B7F
e39E6:
	LDA #$4B
	CMP z92
	BNE e39E3
	JMP RestoreVarsAndRegsAndExit
e39EF:
	; Default configuration template if game does not provide title/logo
	dc.b $80,$01,$f1,$01,$79,$e9,$00,$1a,$4c,$00,$00,$00,$ff,$3c,$0a
	
; ---------------------------------------------------------------
; Font Bitmap Data - Scanline Row 5 ($3A00 - $3A2B)
; ---------------------------------------------------------------
	org $3A00,$FF
FontDataRow5:
	dc.b $c6,$c6,$dc,$cc,$c0,$c0
	dc.b $e6,$c6,$18,$cc,$cc,$fe
	dc.b $c6,$c6,$ee,$c0,$ec,$cc
	dc.b $c6,$18,$ee,$38,$7c,$c6
	dc.b $30,$c6,$00,$38,$00,$00
	dc.b $00,$ff,$10,$ee,$18,$c0
	dc.b $ce,$0c,$ee,$ee,$30,$ee
	dc.b $0c,$00	

;
; 16-bit shift left (Multiply A/Y by 2)
;
e3A2C:
	ASL
	PHA
	TYA
	ROL
	TAY
	PLA
	RTS
	
;
; 16-bit Addition: Add z92/z93 to A/Y and return in A/Y
;
Addz92ToAYStoreInAY:
	CLC
	ADC z92
	PHA
	TYA
	ADC z93
	TAY
	PLA
	RTS

;
; 16-bit Addition: Add z92/z93 to A/Y and store result in z92/z93
;
Addz92ToAYStoreInz92:
	JSR Addz92ToAYStoreInAY
Setupz92WithAY:
	STA z92
	STY z93
	RTS
	
;
; 24-bit Binary Addition: Add zAD..zAF[Y] to zAD..zAF[X]
;
e3A45:
	TXA
	TAY
e3A47:
	LDA zAD,X
	CLC
	ADC zAD,Y
	STA zAD,X
	LDA zAE,X
	ADC zAE,Y
	STA zAE,X
	LDA zAF,X
	ADC zAF,Y
	STA zAF,X
	RTS
	
; ==============================================================================
; Routine: e3A5E
; Purpose: Multiplies 24-bit binary integer in zAD, zAE, zAF by 10.
; ==============================================================================
e3A5E:
	TYA
	PHA
	LDA zAD,X
	STA zB0
	LDA zAE,X
	STA zB1
	LDA zAF,X
	STA zB2
	JSR e3A45                       ; Value * 2
	JSR e3A45                       ; Value * 4
	LDY #$03
	JSR e3A47                       ; (Value * 4) + Original
	JSR e3A45                       ; ((Value * 4) + Original) * 2 = Value * 10
	PLA
	TAY
	RTS
	
; Maximum allowable BCD score: 16,777,215 ($FFFFFF)
e3A7D:
    dc.b $16,$77,$72,$15
e3A81:
    dc.b $a0,$ff

; ==============================================================================
; Routine: e3A83 / e3AA8
; Purpose: Unpacks 4-byte packed BCD score from (ArgGameScoreLo) and converts
;          it into a 24-bit unsigned binary integer stored in zAD, zAE, zAF.
; ==============================================================================
e3A83:
	INY
	CPY #$04
	BEQ e3A9D
	LDA e3A7D,Y
	CMP (ArgGameScoreLo),Y
	BMI e3A93                       ; Clamp to 16,777,215 if exceeded (sign of the
	                                ; difference, so $97-$99 bytes are not clamped)
	BEQ e3A83
	BPL e3A9D
e3A93:
	STA (ArgGameScoreLo),Y          ; Clamped value is written back to the caller's score
	INY
	LDA e3A7D,Y
	CPY #$04
	BNE e3A93
e3A9D:
	LDA #$00
	STA zAD
	STA zAE
	STA zAF
	TAY
	LDX #$00
e3AA8:
	JSR e3A5E                       ; Multiply accumulator by 10
	LDA (ArgGameScoreLo),Y
	PHA
	AND #$F0                        ; Extract high BCD nibble
	LSR
	LSR
	LSR
	LSR
	CLC
	ADC zAD
	STA zAD
	LDA zAE
	ADC #$00                        ; Carry out of zAE is not added into zAF
	STA zAE
	JSR e3A5E                       ; Multiply accumulator by 10
	PLA
	AND #$0F                        ; Extract low BCD nibble
	CLC
	ADC zAD
	STA zAD
	LDA zAE
	ADC #$00
	STA zAE
	INY
	CPY #$04
	BNE e3AA8                       ; Loop for all 4 BCD bytes (8 decimal digits)
	RTS
	
; ==============================================================================
; Routine: e3AD6
; Purpose: Unpacks two 15-bit packed initials bytes into three 5-bit Watkins
;          characters.
; Inputs:  A = Packed Byte 0: [C1.4 C1.3 C1.2 C0.4 C0.3 C0.2 C0.1 C0.0]
;          X = Packed Byte 1: [C1.1 C1.0   0  C2.4 C2.3 C2.2 C2.1 C2.0]
; Outputs: A = Character 0
;          X = Character 1
;          Y = Character 2
; ==============================================================================
e3AD6:
	PHA
	AND #$E0                        ; Extract C1 bits 4..2 from Byte 0
	LSR
	LSR
	LSR
	STA zB1
	TXA
	AND #$1F                        ; Extract C2 bits 4..0 from Byte 1
	TAY                             ; Y = Character 2
	TXA
	AND #$C0                        ; Extract C1 bits 1..0 from Byte 1
	CLC
	ROL
	ROL
	ROL                             ; Rotate into bits 1..0
	ORA zB1
	TAX                             ; X = Character 1
	PLA
	AND #$1F                        ; A = Character 0
	RTS
	
;
; Waits for vertical blank and confirms fire button release, then falls into
; e3AF9, which restarts the screen saver idle timers
;
e3AF0:
	JSR WaitForVBlank
	JSR e397C
	TYA
	BPL e3AF0
e3AF9:
	LDA #$E9
	STA IdleTimerFine
	STA IdleTimerCoarse
	RTS

; ---------------------------------------------------------------
; Font Bitmap Data - Scanline Row 4 ($3B00 - $3B2B)
; ---------------------------------------------------------------
	org $3B00,$FF
FontDataRow4:
	dc.b $fe,$c6,$c0,$c6,$c0,$c0
	dc.b $c6,$c6,$18,$cc,$d8,$c0
	dc.b $c6,$ce,$c6,$f8,$dc,$c8
	dc.b $0e,$18,$c6,$6c,$d6,$6c
	dc.b $30,$60,$00,$00,$00,$00
	dc.b $00,$ff,$20,$e6,$18,$60
	dc.b $04,$fe,$02,$c6,$30,$ee
	dc.b $06,$10

;
; Console switch debounce polling
;
e3B2C:
	LDA #$08
	JSR e3C63
e3B31:
	BIT SWCHB
	BNE e3B3C
	JSR WaitFor2VBlanks
	BIT SWCHB
e3B3C:
	RTS
	
e3B3D:
	LDA #$08
e3B3F:
	STA zB8
	AND SWCHB
	CMP zB8
e3B46:
	RTS
e3B47:
	JSR ScreenSaverWaitForInput
	PHA
	PHA
	JSR e3C63
	PLA
	PLA
e3B51:
	JSR e3B71
	LDA #$00        ; TIA audio off
	STA AUDV0
	STA AUDV1
	LDY #$E9
	STY IdleTimerCoarse
e3B5E:
	DEC IdleTimerCoarse
	BEQ e3B47
e3B62:
	DEC IdleTimerFine
	BEQ e3B5E
	JSR e3B2C
	BNE e3B62
	JSR e3B71
	JMP e3C31
e3B71:
	JSR e3B3D
	BEQ e3B46
	LDA #$0A
	JSR e3B31
	BNE e3B71
	PLA
	PLA
e3B7F:
	LDA #$00
	STA ArgGameController
	STA PlayerController
	JSR DisableMariaDMA
	LDA #$8C
	LDY OrigArgRamHi
	JSR Setupz92WithAY
	LDY #$54
e3B91:
	LDA e3F6F,Y
	STA (z92),Y
	DEY
	BPL e3B91
	LDA #$23
	INC z93
	STA z92
	LDY #$43
e3BA1:
	LDA e3E83,Y
	CMP #$FD
	BNE e3BAA
	LDA OrigArgRamHi
e3BAA:
	STA (z92),Y
	DEY
	BPL e3BA1
	JSR e3FD8
	LDA #<e3DA4
	LDY #>e3DA4
	JSR SetupDllPlusSome
e3BB9:
	LDA #$0A
	JSR e3B3F
	BNE e3BB9
	JSR e320F
	LDA #$4B
	JSR Setupz92WithAY
	LDA #$B6
	STA z9A
e3BCC:
	JSR WaitVBlankAndTickIdleTimer
	DEC zB7
	BNE e3BE3
	LDA #$0A
	STA zB7
	JSR e397C
	AND #$30
	CMP #$30
	BNE e3BF8
	TYA
	BPL e3BFC
e3BE3:
	DEC zB3
	BNE e3BCC
	LDA #$1E
	STA zB3
e3BEB:
	LDY #$03
	JSR e3EF3
	LDY #$06
	JSR e3EF3
	JMP e3BCC
e3BF8:
	PHA
	JMP e383A
e3BFC:
	JMP e387B
	
; ---------------------------------------------------------------
; Font Bitmap Data - Scanline Row 3 ($3C00 - $3C2B)
; ---------------------------------------------------------------
	org $3C00,$FF
FontDataRow3:
	dc.b $c6,$fc,$c0,$c6,$f8,$f8
	dc.b $ce,$fe,$18,$0c,$f0,$c0
	dc.b $d6,$de,$c6,$ce,$c6,$f8
	dc.b $3c,$18,$c6,$c6,$d6,$38
	dc.b $30,$30,$00,$00,$3c,$00
	dc.b $10,$ff,$7c,$d6,$18,$38
	dc.b $38,$cc,$0e,$dc,$18,$38
	dc.b $76,$18

;
; Display hold: every vector except HSCSETRS returns through here. Holds for
; ArgHSDisplayTime x 60 frames; each frame waits for VBLANK, checks the exit
; inputs, checks PAUSE, then calls the sound callback.
;
e3C2C:
	PHA

e3C2D:
	DEC ArgHSDisplayTime
	BMI RestoreVarsAndRegsAndExit
e3C31:
	LDA #$3C
	STA zA8
e3C35:
	JSR e3B2C
	BNE e3C3D
	JMP e3B51
e3C3D:
	JSR e36DB                       ; Call caller sound callback during the hold
	DEC zA8
	BNE e3C35
	BEQ e3C2D

; ==============================================================================
; Routine: RestoreVarsAndRegsAndExit
; Purpose: Restores caller zero page $80-$BC from UserRAM + $13..$4F, restores
;          $94/$95, restores console switches, and exits via RTS.
; ==============================================================================
RestoreVarsAndRegsAndExit:
	LDA zSWCHBSave
	STA SWCHB                       ; Restore RIOT console switch state
	LDX #$3C
	LDY #$13
e3C4F:
	LDA (OrigArgRamLo),Y            ; Restore ZP $80-$BC (61 bytes)
	STA ArgGameID1,X
	INY
	DEX
	BPL e3C4F
	LDA CartRAMSaveZ94
	STA OrigArgRamLo                ; Restore caller ZP $94
	LDA CartRAMSaveZ95
	STA OrigArgRamHi                ; Restore caller ZP $95
	PLA
	RTS

;
; Hold exit check. Level-sensitive: an input already held ends the hold on the
; first frame. P1 fire and any SWCHA bit are skipped for custom controllers.
;
e3C63:
	JSR WaitForVBlank
	PHA
	LDA ArgGameController
	CMP #$02
	BEQ e3C78
	BIT INPT4
	BPL e3C8D
	LDA SWCHA
	CMP #$FF
	BNE e3C8D
e3C78:
	LDA SWCHB
	AND #$03
	EOR #$03
	BNE e3C8D
	JSR e397C
	CMP #$F0
	BNE e3C8D
	TYA
	BPL e3C8D
	PLA
	RTS

;
; User requested exit: pop stack and exit cleanly
;
e3C8D:
	PLA
	PLA
	PLA
	PLA
	PLA
	JMP RestoreVarsAndRegsAndExit

; ==============================================================================
; Routine: e3C95
; Purpose: Sets up and runs the Cartridge Personalization screen (Owner Name).
; ==============================================================================
e3C95:
	JSR DisableMariaDMA
	JSR HSCSETRS2
	LDY OrigArgRamHi
	LDA #$D1
	JSR Setupz92WithAY
	LDY #$28
e3CA4:
	LDA e3F45,Y
	STA (z92),Y
	DEY
	BPL e3CA4
	LDA #$8C
	STA z92
	LDY #$22
e3CB2:
	LDA e3E63,Y
	CMP #$FD
	BNE e3CBB
	LDA OrigArgRamHi
e3CBB:
	STA (z96),Y
	DEY
	BPL e3CB2
	JSR e3FD8
	LDA #<DllDisplayPersonalizeCartLut
	LDY #>DllDisplayPersonalizeCartLut
	JSR SetupDllPlusSome
	LDY #$09
e3CCC:
	STY CartRAMOwnerNameLength
	DEY
e3CD0:
	LDA e3DCC,Y
	STA CartRAMOwnerName,Y
	DEY
	BPL e3CD0
	LDA #$00
	STA PlayerController
	LDA #$20
	STA zA8
	LDA #$8C
	STA z98
	LDY OrigArgRamHi
	STY z99
	LDX #$1A
	JSR e3389                       ; Run owner name character input loop
	LDY OrigArgRamHi
	LDA #$8C
	JSR Setupz92WithAY
	LDY #$1F
e3CF7:
	LDA (z92),Y
	STA CartRAMOwnerName,Y
	DEY
	BPL e3CF7
	RTS

; ---------------------------------------------------------------
; Font Bitmap Data - Scanline Row 2 ($3D00 - $3D2B)
; ---------------------------------------------------------------
	org $3D00,$FF
FontDataRow2:
	dc.b $6c,$c8,$c0,$c6,$c0,$c0
	dc.b $c0,$c6,$18,$0c,$d0,$c0
	dc.b $ee,$f6,$c6,$c6,$c6,$ce
	dc.b $e0,$18,$c6,$c6,$c6,$38
	dc.b $38,$18,$00,$00,$00,$00
	dc.b $18,$ff,$20,$ce,$18,$0c
	dc.b $04,$6c,$f8,$c0,$0c,$ee
	dc.b $c6,$0c
	
;
; Palette configuration values (P0C2 - P4C2)
;
e3D2C:
	dc.b $9d,$0a,$96,$98,$fa
e3D31:
        ; Encoded DLL data for compact score display
        dc.b $8f,$61,$05
        dc.b $06,$61,$00
        dc.b $06,$61,$05
        dc.b $06,$61,$07
DllDisplayFiveScoresLut:
        ; Encoded DLL data for standard five-score table display
        dc.b $8f,$61,$05
        dc.b $06,$61,$1c 
 ifnconst PALHACK
        dc.b $06,$61,$05 ; 3D43
 else
        dc.b $0E,$61,$05 ; 3D43
 endif
        dc.b $06,$61,$23
 ifnconst PALHACK
        dc.b $06,$61,$05 ; 3D49
 else
        dc.b $0E,$61,$05 ; 3D49
 endif
        dc.b $06,$61,$37
 ifnconst PALHACK
        dc.b $06,$61,$05 ; 3D4F 0F
 else
        dc.b $0E,$61,$05 ; 3D4F 0F
 endif
        dc.b $06,$61,$4b
 ifnconst PALHACK
        dc.b $06,$61,$05 ; 3D55 0F
 else
        dc.b $0E,$61,$05 ; 3D55 0F
 endif
        dc.b $06,$61,$5f
 ifnconst PALHACK
        dc.b $06,$61,$05 ; 3D5B 0F
 else
        dc.b $0E,$61,$05 ; 3D5B 0F
 endif
        dc.b $06,$61,$73
 ifnconst PALHACK
        dc.b $06,$61,$05 ; 3D61 0F
 else
        dc.b $0F,$61,$05 ; 3D61 0F
 endif
 ifnconst PALHACK
        dc.b $00 
 endif

DllDisplayGameNameHeaderLut:
        ; Encoded DLL data for the text game-name header (used when no logo)
 ifnconst PALHACK
        dc.b $09,$61,$05 ; 3D65 0F
 else
        dc.b $0F,$61,$05 ; 3D65 0F
 endif
	dc.b $0f,$61,$05
        dc.b $0f,$61,$05
	dc.b $06,$61,$0e
        dc.b $0f,$61,$05
	dc.b $00
 ifconst PALHACK
        dc.b $00 
 endif

DllDisplayGameDifficultyLut:
        ; Encoded DLL data for game difficulty header string
 ifnconst PALHACK
        dc.b $86,$61,$05 ; 3D75 8F
 else
        dc.b $8E,$61,$05 ; 3D75 8F
 endif
        dc.b $06,$61,$15
        dc.b $00
DllDisplayPersonalizeCartLut:
        ; Encoded DLL data for cartridge personalization screen
        dc.b $0f,$61,$05
        dc.b $08,$61,$05
        dc.b $0f,$61,$05
	dc.b $0f,$61,$05
        dc.b $06,$61,$05
        dc.b $06,$61,$00
        dc.b $0b,$61,$05
        dc.b $06,$61,$07
        dc.b $0b,$61,$05
        dc.b $06,$61,$0e
        dc.b $0f,$61,$05
        dc.b $04,$61,$05
        dc.b $06,$61,$15
        dc.b $00
e3DA4:
        ; Encoded DLL data for menu display
	dc.b $0f,$61,$05
        dc.b $0f,$61,$05
	dc.b $0f,$61,$05
        dc.b $06,$61,$23
	dc.b $0f,$61,$05
        dc.b $0f,$61,$05
	dc.b $06,$61,$2a
        dc.b $0b,$61,$05
	dc.b $06,$61,$35
        dc.b $0b,$61,$05
	dc.b $06,$61,$40
        dc.b $0b,$61,$05
	dc.b $06,$61,$4b
        dc.b $00
	
; Default owner name "A T A R I" (9 bytes), written before name entry starts
e3DCC:
	dc.b $00,$1d,$13,$1d,$00,$1d,$11,$1d,$08

; ==============================================================================
; Routine: AdvanceToNextScoreEntry
; Purpose: Advances pointer indices Y and X to the start of the next 3-byte
;          binary score entry in Cart NVRAM.
; ==============================================================================
AdvanceToNextScoreEntry:
	DEY
	DEX
	CPX #$FE
	BNE AdvanceToNextScoreEntry
	RTS

; ==============================================================================
; Routine: IsScoreNonZero
; Purpose: Checks if the 24-bit candidate binary score in zAD..zAF is non-zero.
; Outputs: Z flag set (1) if score is 0; Z flag clear (0) if score > 0.
; ==============================================================================
IsScoreNonZero:
	LDA zAD
	BNE IsScoreNonZero_Exit
	LDA zAE
	BNE IsScoreNonZero_Exit
	LDA zAF
IsScoreNonZero_Exit:
	RTS
	
;
; Builds repeated display list entries
;
e3DE7:
	STA zB3
e3DE9:
	LDA (z96),Y
	LDX #$04
e3DED:
	PHA
	TYA
	CLC
	ADC #$14
	TAY
	PLA
	CLC
	ADC zB3
	STA (z96),Y
	DEX
	BNE e3DED
	RTS
	
; ---------------------------------------------------------------
; Font Bitmap Data - Scanline Row 1 ($3E00 - $3E2B)
; ---------------------------------------------------------------
	org $3E00,$FF
FontDataRow1:
	dc.b $78,$cc,$dc,$cc,$c0,$c0
	dc.b $e0,$c6,$18,$0c,$c8,$c0
	dc.b $c6,$e6,$ee,$ce,$ee,$ce
	dc.b $e6,$7e,$c6,$c6,$c6,$6c
	dc.b $6c,$cc,$00,$00,$00,$00
	dc.b $0c,$ff,$10,$ee,$78,$c6
	dc.b $ce,$3c,$c0,$e0,$ce,$ee
	dc.b $ec,$cc

; Display list templates for screen formatting
e3E2C:
	dc.b $c0,$60,$fd,$11,$30,$00
	dc.b $00,$d1,$60,$fd,$18,$40
	dc.b $00,$00,$8c,$60,$fd,$66
	dc.b $1c,$00,$00,$a6,$60,$fd
	dc.b $66,$1c,$00,$00,$da,$60
	dc.b $fd

e3E4B:
    dc.b $80

e3E4C:
    dc.b $50,$00,$00
	
e3E4F:
    dc.b $22,$5f,$39,$32,$53,$60
    dc.b $60,$37,$3a,$50,$60,$60
    dc.b $3d,$62,$00,$00,$39,$62
    dc.b $00,$00

e3E63:
    dc.b $da,$60,$fd,$15,$3a,$00
    dc.b $00,$e5,$60,$fd,$0b,$26
    dc.b $00,$00,$d1,$60,$fd,$17
    dc.b $3e,$00,$00,$8c,$60,$fd
    dc.b $1f,$4e,$00,$00,$39,$4e
    dc.b $00,$00

e3E83:
    dc.b $8c,$60,$fd,$83,$16,$00
    dc.b $00,$a9,$60,$fd,$57,$18
    dc.b $1f,$7f,$39,$46,$00,$00
    dc.b $c1,$60,$fd,$57,$18,$1f
    dc.b $7f,$39,$46,$00,$00,$b2
    dc.b $60,$fd,$51,$00,$1f,$7f
    dc.b $39,$46,$00,$00,$ca,$60
    dc.b $fd,$59,$70,$1f,$7f,$39
    dc.b $96,$00,$00,$d1,$60,$fd
    dc.b $93

e3EBA:
    dc.b $24,$de,$60,$fd,$7d,$60
    dc.b $ca,$60,$fd,$3e,$74,$00
    dc.b $00

; ==============================================================================
; Table: Powers of 10 Subtraction Constants (24-bit Little-Endian)
; Used by e37BC to convert 24-bit binary integers into decimal digits
; ==============================================================================
e3EC7:
    dc.b $80                        ; 10,000,000 ($989680) - LSB
e3EC8:
    dc.b $96                        ; 10,000,000 - MID
e3EC9:
    dc.b $98                        ; 10,000,000 - MSB
e3ECA:
    dc.b $40,$42,$0f                ;  1,000,000 ($0F4240)
    dc.b $a0,$86,$01                ;    100,000 ($0186A0)
    dc.b $10,$27,$00                ;     10,000 ($002710)
    dc.b $e8,$03,$00                ;      1,000 ($0003E8)
    dc.b $64,$00,$00                ;        100 ($000064)
    dc.b $0a,$00,$00                ;         10 ($00000A)
    dc.b $01,$00,$00                ;          1 ($000001)

;
; Display string attribute manipulation routines
;
e3EDF:
	LDA (z92),Y
	CLC
	ADC #$50
e3EE4:
	STA (z92),Y
	RTS

e3EE7:
	INY
e3EE8:
	INY
	LDA (z92),Y
	SEC
	SBC #$50
	BPL e3EE4
e3EF0:
	JSR e3EF9
e3EF3:
	LDA (z92),Y
	EOR #$40
	BNE e3EE4
	
e3EF9:
    dc.b $b1
e3EFA:
    dc.b $92
e3EFB:
    dc.b $09
e3EFC:
    dc.b $40,$d0,$e5

; ---------------------------------------------------------------
; Font Bitmap Data - Scanline Row 0 ($3F00 - $3F2B)
; ---------------------------------------------------------------
	org $3F00,$FF
FontDataRow0:
    dc.b $10,$f8,$70,$f8,$fc,$fe
    dc.b $3c,$c6,$7e,$7e,$cc,$c0
    dc.b $82,$c6,$38,$f8,$38,$f8
    dc.b $7c,$7e,$c6,$c6,$c6,$c6
    dc.b $c6,$fe,$00,$00,$00,$00
    dc.b $04,$ff,$00,$38,$38,$7c
    dc.b $7c,$1c,$fe,$38,$fe,$38
    dc.b $38,$f8

; ---------------------------------------------------------------
; Watkins Stored String Constants
; ---------------------------------------------------------------
e3F2C:
	WATKINS "C"
	WATKINS "O"
	WATKINS "N"
	WATKINS "G"
	WATKINS "R"
	WATKINS "A"
	WATKINS "T"
	WATKINS "U"
	WATKINS "L"
	WATKINS "A"
	WATKINS "T"
	WATKINS "I"
	WATKINS "O"
	WATKINS "N"
	WATKINS "S"
    dc.b $1D
    dc.b $1D
	WATKINS "P"
	WATKINS "L"
	WATKINS "A"
	WATKINS "Y"
	WATKINS "E"
	WATKINS "R"
    dc.b $1D
    dc.b $22
e3F45:
	WATKINS "C"
	WATKINS "A"
	WATKINS "R"
	WATKINS "T"
	WATKINS "R"
	WATKINS "I"
	WATKINS "D"
	WATKINS "G"
	WATKINS "E"

	WATKINS "P"
	WATKINS "E"
	WATKINS "R"
	WATKINS "S"
	WATKINS "O"
	WATKINS "N"
	WATKINS "A"
	WATKINS "L"
	WATKINS "I"
	WATKINS "Z"
	WATKINS "E"

	WATKINS "Y"
	WATKINS "O"
	WATKINS "U"
	WATKINS "R"
    dc.b $1D
	WATKINS "A"
	WATKINS "T"
	WATKINS "A"
	WATKINS "R"
	WATKINS "I"
    dc.b $1D
	WATKINS "H"
	WATKINS "I"
	WATKINS "G"
	WATKINS "H"
    dc.b $1D
	WATKINS "S"
	WATKINS "C"
	WATKINS "O"
	WATKINS "R"
	WATKINS "E"

	WATKINS "A"
e3F6F:
	WATKINS "W"
	WATKINS "H"
	WATKINS "A"
	WATKINS "T"
    dc.b $1D
	WATKINS "W"
	WATKINS "O"
	WATKINS "U"
	WATKINS "L"
	WATKINS "D"
    dc.b $1D
	WATKINS "Y"
	WATKINS "O"
	WATKINS "U"
    dc.b $1D
	WATKINS "L"
	WATKINS "I"
	WATKINS "K"
	WATKINS "E"
    dc.b $1D
	WATKINS "T"
	WATKINS "O"
    dc.b $1D
	WATKINS "C"
	WATKINS "L"
	WATKINS "E"
	WATKINS "A"
	WATKINS "R"
    dc.b $2B                        ; Watkins '?' ($2B)
	WATKINS "T"
	WATKINS "H"
	WATKINS "I"
	WATKINS "S"
    dc.b $1D
	WATKINS "G"
	WATKINS "A"
	WATKINS "M"
	WATKINS "E"

	WATKINS "P"
	WATKINS "E"
	WATKINS "R"
	WATKINS "S"
	WATKINS "O"
	WATKINS "N"
	WATKINS "A"
	WATKINS "L"
	WATKINS "I"
	WATKINS "Z"
	WATKINS "A"
	WATKINS "T"
	WATKINS "I"
	WATKINS "O"
	WATKINS "N"

	WATKINS "A"
	WATKINS "L"
	WATKINS "L"
    dc.b $1D
	WATKINS "G"
	WATKINS "A"
	WATKINS "M"
	WATKINS "E"
	WATKINS "S"

	WATKINS "N"
	WATKINS "O"
	WATKINS "T"
	WATKINS "H"
	WATKINS "I"
	WATKINS "N"
	WATKINS "G"

	WATKINS "A"
	WATKINS "R"
	WATKINS "E"
    dc.b $1D
	WATKINS "Y"
	WATKINS "O"
	WATKINS "U"
    dc.b $1D
	WATKINS "S"
	WATKINS "U"
	WATKINS "R"
	WATKINS "E"
    dc.b $2B                        ; Watkins '?' ($2B)
	WATKINS "Y"
	WATKINS "E"
	WATKINS "S"

; ==============================================================================
; Routine: e3FC4
; Purpose: Erases active game slot from directory (sets difficulty & index to $7F).
;          Uses zB6: the matched slot, or the LRU victim if no table was found.
; ==============================================================================
e3FC4:
	LDX zB6
e3FC6:
	LDA #$7F
	STA CartRAMGameDifficultyTable,X
	STA CartRAMGameAgeTable,X
	RTS

; ==============================================================================
; Routine: e3FCF
; Purpose: Formats entire directory (resets all 69 slots to $7F).
; ==============================================================================
e3FCF:
	LDX #$44
e3FD1:
	JSR e3FC6
	DEX
	BPL e3FD1
	RTS

;
; Resets DLL write pointers to Page N+1 : $87
;
e3FD8:
	LDA #$00
	STA zB2
	STA IdleTimerFine
	LDA #$87
	STA zA4
	LDA z97
	STA zA5
	RTS
	
; ==============================================================================
; HSC PUBLIC JUMP VECTORS ($3FEB - $3FFD)
; ==============================================================================
	org $3feb,$FF
JumpVectors:
; Vector @ $3FEB: HSCSETRS - Initialize MARIA registers and font palette
	JMP HSCSETRS

; Vector @ $3FEE: HSCCPYFM - Copy 25-byte score block from NVRAM to buffer ($8F)
	JMP e3000

; Vector @ $3FF1: HSCCPYTO - Copy 25-byte score block from buffer ($8F) to NVRAM
	JMP e3011

; Vector @ $3FF4: HSCENTER2 - Enter high score, initials from argument +$0F..+$11
	JMP EnterHighScore_SuppliedInitials

; Vector @ $3FF7: HSCSTAT - Check status, rank candidate score, return top score
	JMP HSCSTAT

; Vector @ $3FFA: HSCATRCT - Show one game/difficulty high-score table
	JMP ShowHighScores

; Vector @ $3FFD: HSCENTER - Interactive initials entry and score ranking
	JMP EnterHighScore