; =============================================================================
; CBS Cruncher CBT1 - compact Screen 5 direct-VRAM decruncher v0.1
; =============================================================================
; Uses one v5.5/C5 core for both the ordinary prefix and the compressed tile
; command stream. The core is specialized to Screen 5. Public entries remain
; E000h/E003h/E006h/E009h; E003h accepts A=5 and returns success. The RAM
; resident section ends at CBT_RAM_END; CBS_INIT_MSX is deliberately last so
; a future ROM-resident initializer can be excluded from permanent RAM.

	ORG	E000h

; Fixed decoder routing thresholds. They are not calculated from the image list.
; Offset-1 runs of 64+ bytes use the VDP fill path; matches of 1-8 bytes use
; the small CPU scratch path, while larger matches use VDP-to-VDP copying.
CBS_VD_FAST_FILL_MIN:		EQU	40h
CBS_VD_TINY_MATCH_MAX:		EQU	08h

; Public entry table:
;   E000h / CBS_INIT_MSX    call exactly once after copying the decoder to RAM
;   E003h / CBS_INIT_SCREEN A = 5, 6, 7 or 8; call when Screen mode changes
;   E006h / CBS_DECRUNCH    CBT1 tile-image decrunch entry
;   E009h / CBS_WAIT_VDP    wait until the current VDP command finishes
;
; CBS_INIT_MSX reads VDP.DR/VDP.DW from BIOS addresses 0006h/0007h and patches
; all immediate I/O operands. CBS_INIT_SCREEN patches the pixel, line and page
; shifts. Neither initializer is executed inside the token or match loops.
CBS_INIT_MSX:
	JP	CBS_VD_INIT_MSX
CBS_INIT_SCREEN:
	SUB	05h
	RET
CBS_DECRUNCH:
	JP	CBT_TILE_DECRUNCH
CBS_WAIT_VDP:
	JP	CBS_VD_WAIT_COMMAND

CBS_V5354_DIRECT_VRAM:
	AND	Ch
	RRCA
	RRCA
	LD	(CBS_VD_CURRENT_Y+1),A
	LD	A,E
	LD	(CBS_VD_CURRENT_Y),A
	LD	A,D
	LD	(CBS_VD_START_X),A
	XOR	A
	LD	(CBS_VD_START_X+1),A
	LD	(CBS_VD_COLUMN),A
	LD	(CBS_VD_COLUMN+1),A

	INC	HL			; skip C5h route byte
	LD	A,(HL)
	INC	HL
	PUSH	HL			; preserve height-byte address
	LD	L,A
	LD	H,00h
	OR	A
	JR	NZ,CBS_VD_WIDTH_READY
	INC	H			; zero width means 256 bytes
CBS_VD_WIDTH_READY:
	LD	(CBS_VD_WIDTH),HL
	POP	HL
	INC	HL			; skip height; HL = payload
	XOR	A
	LD	(CBS_VD_COMMAND_ACTIVE),A
	LD	BC,FFFFh
	CALL	CBS_VD_STORE_OFFSET			; default offset = -1
	LD	A,80h			; control-bit sentinel
	EI				; parsing and CPU transfers remain interruptible
	JP	CBS_VD_ROW_START
; -----------------------------------------------------------------------------
; C5h uses the complete v5.5 grammar: ordinary literals, previous/general
; offsets and the whole-line-distance shortcut. It shares the same unrestricted
; VRAM match backend as 45h/4Dh.
; -----------------------------------------------------------------------------
CBS_VD_ROW_START:
	CALL	CBS_VD_ELIAS
	JR	CBS_VD_ROW_COPY_LITERALS

CBS_VD_ROW_AFTER_LITERAL:
	CALL	CBS_VD_GET_BIT
	JR	C,CBS_VD_ROW_NEW_OFFSET
	CALL	CBS_VD_ELIAS
	CALL	CBS_VD_IS_LENGTH_ONE
	JR	NC,CBS_VD_ROW_COPY_MATCH
	CALL	CBS_VD_GET_BIT
	JP	C,CBS_VD_FINISHED
	LD	BC,0001h
	JR	CBS_VD_ROW_COPY_MATCH

CBS_VD_ROW_AFTER_MATCH:
	CALL	CBS_VD_GET_BIT
	JR	C,CBS_VD_ROW_NEW_OFFSET
	CALL	CBS_VD_ELIAS
	CALL	CBS_VD_IS_LENGTH_ONE
	JR	NC,CBS_VD_ROW_COPY_LITERALS
	CALL	CBS_VD_GET_BIT
	JP	C,CBS_VD_FINISHED
	LD	BC,0001h

CBS_VD_ROW_COPY_LITERALS:
	LD	(CBS_VD_REMAIN),BC
	CALL	CBS_VD_WRITE_LITERALS
	JR	CBS_VD_ROW_AFTER_LITERAL

CBS_VD_ROW_COPY_MATCH:
	LD	(CBS_VD_REMAIN),BC
	PUSH	AF
	PUSH	HL
CBS_VD_ROW_MATCH_CALL:
	CALL	CBS_VD_COPY_MATCH_LOOP
	POP	HL
	POP	AF
	JR	CBS_VD_ROW_AFTER_MATCH

CBS_VD_ROW_NEW_OFFSET:
	CALL	CBS_VD_GET_BIT
	JR	C,CBS_VD_ROW_GENERAL_OFFSET

	LD	BC,FFFEh
	CALL	CBS_VD_ELIAS_LOOP
	INC	BC			; negative whole-line distance
	PUSH	AF
	PUSH	HL
	LD	HL,0000h
	OR	A
	SBC	HL,BC
	LD	(CBS_VD_OFFSET_ROWS),HL
	LD	HL,0000h
	LD	(CBS_VD_OFFSET_COLUMNS),HL
	LD	DE,(CBS_VD_WIDTH)
	LD	HL,0000h
	LD	A,10h
CBS_VD_ROW_MULTIPLY:
	ADD	HL,HL
	SLA	C
	RL	B
	JR	NC,CBS_VD_ROW_MULTIPLY_SKIP
	ADD	HL,DE
CBS_VD_ROW_MULTIPLY_SKIP:
	DEC	A
	JR	NZ,CBS_VD_ROW_MULTIPLY
	LD	B,H
	LD	C,L			; negative byte distance
	POP	HL
	POP	AF
	PUSH	HL
	PUSH	AF
	LD	HL,0000h
	OR	A
	SBC	HL,BC
	LD	(CBS_VD_DISTANCE),HL
	CALL	CBS_VD_SELECT_MATCH_ROUTE
	POP	AF
	POP	HL
	CALL	CBS_VD_ELIAS
	INC	BC
	JR	CBS_VD_ROW_COPY_MATCH

CBS_VD_ROW_GENERAL_OFFSET:
	LD	C,FEh
	CALL	CBS_VD_ELIAS_LOOP
	INC	C
	LD	B,C
	LD	C,(HL)
	INC	HL
	RR	B
	RR	C
	CALL	CBS_VD_STORE_OFFSET
	LD	BC,0001h
	CALL	NC,CBS_VD_ELIAS_BACKTRACK
	INC	BC
	JR	CBS_VD_ROW_COPY_MATCH

CBS_VD_IS_LENGTH_ONE:
	PUSH	AF
	LD	A,B
	OR	A
	JR	NZ,CBS_VD_LENGTH_NOT_ONE
	LD	A,C
	DEC	A
	JR	NZ,CBS_VD_LENGTH_NOT_ONE
	POP	AF
	SCF
	RET
CBS_VD_LENGTH_NOT_ONE:
	POP	AF
	OR	A
	RET

CBS_VD_FINISHED:
; -----------------------------------------------------------------------------
; Only another VDP command or decoder exit must wait for CE to clear. Literal
; output through port 98h may continue while an earlier HMMM is active.
; -----------------------------------------------------------------------------
CBS_VD_WAIT_IF_COMMAND:
	LD	A,00h
CBS_VD_COMMAND_ACTIVE:		EQU	$ - 1
	OR	A
	RET	Z
	JP	CBS_VD_WAIT_COMMAND

; Public unconditional wait entry. The controller enters with interrupts
; disabled, so each poll selects status 2 atomically, restores status 0, then
; opens an interrupt window while the command engine continues. Return with
; interrupts enabled so a following HALT can expose one complete black frame.
CBS_VD_WAIT_COMMAND:
	LD	A,(0007h)		; BIOS VDP write base port
	INC	A
	LD	C,A			; C = VDP register/status port
CBS_VD_WAIT_PUBLIC_LOOP:
	DI
	LD	A,02h
	OUT	(C),A
	LD	A,8Fh
	OUT	(C),A			; select status register 2
	IN	A,(C)
	RRCA				; Carry = CE
	LD	A,00h			; preserve Carry while restoring R15
	OUT	(C),A
	LD	A,8Fh
	EI				; takes effect after the following OUT
	OUT	(C),A			; restore status register 0 atomically
	JR	C,CBS_VD_WAIT_PUBLIC_LOOP
	XOR	A
	LD	(CBS_VD_COMMAND_ACTIVE),A
	RET

; -----------------------------------------------------------------------------
; Store a negative byte offset in BC. General offsets are divided once into a
; positive row distance and column distance. AF, including the length backtrack
; Carry, is preserved.
; -----------------------------------------------------------------------------
CBS_VD_STORE_OFFSET:
	PUSH	HL			; preserve packed-stream pointer
	PUSH	AF
	LD	HL,0000h
	OR	A
	SBC	HL,BC
	LD	(CBS_VD_DISTANCE),HL
	LD	DE,(CBS_VD_WIDTH)
	LD	BC,0000h
	LD	A,10h

; Divide the positive distance in HL by the positive packed width in DE.
; Quotient rows remain in HL and remainder columns in BC. The fixed sixteen
; iterations avoid a new data-dependent loop for narrow images.
CBS_VD_DIVIDE_LOOP:
	ADD	HL,HL
	RL	C
	RL	B
	PUSH	HL
	LD	H,B
	LD	L,C
	OR	A
	SBC	HL,DE
	JR	C,CBS_VD_DIVIDE_RESTORE
	LD	B,H
	LD	C,L
	POP	HL
	INC	L
	JR	CBS_VD_DIVIDE_NEXT

CBS_VD_DIVIDE_RESTORE:
	POP	HL

CBS_VD_DIVIDE_NEXT:
	DEC	A
	JR	NZ,CBS_VD_DIVIDE_LOOP
	LD	(CBS_VD_OFFSET_ROWS),HL
	LD	H,B
	LD	L,C
	LD	(CBS_VD_OFFSET_COLUMNS),HL
	CALL	CBS_VD_SELECT_MATCH_ROUTE

	POP	AF
	POP	HL
	RET

; Whole-row v5.5 offsets cache their geometry before arriving here. Both new
; offset paths call this selector, so neither keeps a private one-call helper.
CBS_VD_SELECT_MATCH_ROUTE:
	LD	HL,(CBS_VD_DISTANCE)
	LD	DE,CBS_VD_COPY_MATCH_LOOP
	DEC	HL
	LD	A,H
	OR	L
	JR	NZ,CBS_VD_STORE_MATCH_ROUTE
	LD	DE,CBS_VD_COPY_OFFSET1
CBS_VD_STORE_MATCH_ROUTE:
	LD	(CBS_VD_ROW_MATCH_CALL+1),DE
	RET

; -----------------------------------------------------------------------------
; Emit a literal run through port 98h. Each OTIR ends at a destination row
; boundary, and B=0 deliberately represents 256 bytes.
; -----------------------------------------------------------------------------
CBS_VD_WRITE_LITERALS:
	PUSH	AF

CBS_VD_LITERAL_LOOP:
	PUSH	HL
	CALL	CBS_VD_CHOOSE_DEST_CHUNK
	CALL	CBS_VD_SET_WRITE_ADDRESS
	POP	HL
	LD	BC,0000h
CBS_VD_CHUNK:			EQU	$ - 2
	LD	B,C
CBS_VD_PORT0_LITERAL:
	LD	C,98h
	OTIR
	PUSH	HL
	CALL	CBS_VD_ADVANCE
	CALL	CBS_VD_SUBTRACT_CHUNK
	POP	HL
	JR	NZ,CBS_VD_LITERAL_LOOP
	POP	AF
	RET

; -----------------------------------------------------------------------------
; Copy a match from already expanded VRAM. Recalculate the source coordinate
; for each fragment because an overlapping match may consume its own earlier
; fragments. Complete aligned rows use one rectangular HMMM, capped to the
; already-expanded row distance. Other chunks remain <= distance for LDIR-style
; forward propagation.
; -----------------------------------------------------------------------------
CBS_VD_COPY_OFFSET1:
	LD	HL,(CBS_VD_REMAIN)
	LD	A,H
	OR	A
	JR	NZ,CBS_VD_OFFSET1_READ
	LD	A,L
	CP	CBS_VD_FAST_FILL_MIN
	JR	NC,CBS_VD_OFFSET1_READ

; Read the preceding packed VRAM byte once for either CPU repetition or HMMV.
CBS_VD_OFFSET1_READ:
	CALL	CBS_VD_WAIT_IF_COMMAND
	DI				; R14 and VRAM read-address sequence is atomic
	LD	HL,(CBS_VD_CURRENT_Y)
	LD	DE,(CBS_VD_COLUMN)
	LD	A,D
	OR	E
	JR	NZ,CBS_VD_OFFSET1_PREVIOUS_COLUMN
	DEC	HL
	LD	DE,(CBS_VD_WIDTH)
	DEC	DE			; preceding row ends at width-1
	JR	CBS_VD_OFFSET1_PREVIOUS_COORDINATE

CBS_VD_OFFSET1_PREVIOUS_COLUMN:
	DEC	DE

CBS_VD_OFFSET1_PREVIOUS_COORDINATE:
	LD	B,H			; selected VRAM page
	LD	H,00h			; Y within page
CBS_VD_OFFSET1_READ_SCREEN_LINE_SHIFT:
	LD	A,07h			; patched by CBS_INIT_SCREEN

CBS_VD_OFFSET1_READ_LINE_SHIFT:
	ADD	HL,HL
	DEC	A
	JR	NZ,CBS_VD_OFFSET1_READ_LINE_SHIFT
	PUSH	DE
	LD	DE,(CBS_VD_START_X)
	ADD	HL,DE
	POP	DE
	ADD	HL,DE

	PUSH	HL
	LD	A,H
	AND	C0h
	RLCA
	RLCA
	LD	C,A
	LD	A,B
CBS_VD_OFFSET1_READ_SCREEN_PAGE_SHIFT:
	LD	B,01h			; patched by CBS_INIT_SCREEN

CBS_VD_OFFSET1_READ_PAGE_SHIFT:
	ADD	A,A
	DJNZ	CBS_VD_OFFSET1_READ_PAGE_SHIFT
	OR	C
CBS_VD_PORT1_OFFSET1_R14_VALUE:
	OUT	(99h),A
	LD	A,8Eh
CBS_VD_PORT1_OFFSET1_R14_SELECT:
	OUT	(99h),A
	POP	HL
	LD	A,L
CBS_VD_PORT1_OFFSET1_ADDRESS_LOW:
	OUT	(99h),A
	LD	A,H
	AND	3Fh
CBS_VD_PORT1_OFFSET1_ADDRESS_HIGH:
	OUT	(99h),A
	EI
CBS_VD_PORT0_OFFSET1_READ:
	IN	A,(98h)
	LD	(CBS_VD_FILL_COLOR_LOAD+1),A

	LD	HL,(CBS_VD_REMAIN)
	LD	A,H
	OR	A
	JR	NZ,CBS_VD_FAST_FILL_LOOP
	LD	A,L
	CP	CBS_VD_FAST_FILL_MIN
	JR	NC,CBS_VD_FAST_FILL_LOOP

; Short offset-1 matches are cheaper as direct CPU repetitions than as a
; sequence of one-byte HMMM commands.
CBS_VD_SHORT_FILL_LOOP:
	CALL	CBS_VD_CHOOSE_DEST_CHUNK
	CALL	CBS_VD_SET_WRITE_ADDRESS
	LD	A,(CBS_VD_CHUNK)
	LD	B,A
CBS_VD_PORT0_SHORT_FILL:
	LD	C,98h
CBS_VD_FILL_COLOR_LOAD:
	LD	A,00h
CBS_VD_SHORT_FILL_BYTE:
	OUT	(C),A
	DJNZ	CBS_VD_SHORT_FILL_BYTE
	CALL	CBS_VD_ADVANCE
	CALL	CBS_VD_SUBTRACT_CHUNK
	JR	NZ,CBS_VD_SHORT_FILL_LOOP
	RET

CBS_VD_FAST_FILL_LOOP:
	CALL	CBS_VD_WAIT_IF_COMMAND
	LD	HL,(CBS_VD_COLUMN)
	LD	A,H
	OR	L
	JR	NZ,CBS_VD_FAST_FILL_FRAGMENT

	LD	HL,(CBS_VD_REMAIN)
	LD	DE,(CBS_VD_WIDTH)
	LD	BC,0000h

CBS_VD_FILL_ROW_COUNT:
	OR	A
	SBC	HL,DE
	JR	C,CBS_VD_FILL_ROWS_READY
	INC	BC
	JR	CBS_VD_FILL_ROW_COUNT

CBS_VD_FILL_ROWS_READY:
	ADD	HL,DE			; HL = trailing partial-row bytes
	LD	A,B
	OR	C
	JR	Z,CBS_VD_FAST_FILL_FRAGMENT

	PUSH	BC			; preserve complete-row count
	LD	DE,(CBS_VD_REMAIN)
	EX	DE,HL			; HL=remaining, DE=trailing bytes
	OR	A
	SBC	HL,DE			; HL=packed bytes in full rows
	LD	(CBS_VD_FILL_ADVANCE),HL
	LD	HL,(CBS_VD_WIDTH)
	LD	(CBS_VD_HMMV_NX),HL
	POP	BC
	LD	(CBS_VD_HMMV_NY),BC
	CALL	CBS_VD_START_HMMV
	LD	HL,(CBS_VD_CURRENT_Y)
	LD	DE,(CBS_VD_HMMV_NY)
	ADD	HL,DE
	LD	(CBS_VD_CURRENT_Y),HL
	LD	HL,0000h
CBS_VD_FILL_ADVANCE:		EQU	$ - 2
	LD	(CBS_VD_CHUNK),HL
	JR	CBS_VD_FAST_FILL_SUBTRACT

CBS_VD_FAST_FILL_FRAGMENT:
	CALL	CBS_VD_CHOOSE_DEST_CHUNK
	LD	HL,(CBS_VD_CHUNK)
	LD	(CBS_VD_HMMV_NX),HL
	LD	HL,0001h
	LD	(CBS_VD_HMMV_NY),HL
	CALL	CBS_VD_START_HMMV
	CALL	CBS_VD_ADVANCE

CBS_VD_FAST_FILL_SUBTRACT:
	CALL	CBS_VD_SUBTRACT_CHUNK
	JR	NZ,CBS_VD_FAST_FILL_LOOP
	RET

CBS_VD_COPY_MATCH_LOOP:
	LD	HL,(CBS_VD_COLUMN)
	LD	DE,0000h
CBS_VD_OFFSET_COLUMNS:		EQU	$ - 2
	LD	BC,0000h
	OR	A
	SBC	HL,DE
	JR	NC,CBS_VD_SOURCE_COLUMN_READY
	LD	DE,0000h
CBS_VD_WIDTH:			EQU	$ - 2
	ADD	HL,DE
	INC	BC

CBS_VD_SOURCE_COLUMN_READY:
	LD	(CBS_VD_SOURCE_COLUMN),HL
	LD	HL,(CBS_VD_CURRENT_Y)
	LD	DE,0000h
CBS_VD_OFFSET_ROWS:		EQU	$ - 2
	OR	A
	SBC	HL,DE
	OR	A
	SBC	HL,BC
	LD	(CBS_VD_SOURCE_Y),HL

	LD	HL,(CBS_VD_COLUMN)
	LD	DE,(CBS_VD_SOURCE_COLUMN)
	LD	A,H
	OR	L
	OR	D
	OR	E
	JR	NZ,CBS_VD_MATCH_FRAGMENT

; Both coordinates begin at the left edge. Count complete destination rows,
; but never exceed the source row distance: every byte read by this HMMM was
; therefore complete before the command began.
	LD	HL,(CBS_VD_OFFSET_ROWS)
	LD	B,L			; image height is at most 256; distance is at most 255
	LD	A,B
	OR	A
	JR	Z,CBS_VD_MATCH_FRAGMENT
	LD	HL,(CBS_VD_REMAIN)
	LD	DE,(CBS_VD_WIDTH)
	LD	C,00h

CBS_VD_MATCH_ROW_COUNT:
	OR	A
	SBC	HL,DE
	JR	C,CBS_VD_MATCH_ROWS_TRAILING
	INC	C
	DJNZ	CBS_VD_MATCH_ROW_COUNT
	JR	CBS_VD_MATCH_ROWS_READY

CBS_VD_MATCH_ROWS_TRAILING:
	ADD	HL,DE			; restore bytes after the complete rows

CBS_VD_MATCH_ROWS_READY:
	LD	A,C
	OR	A
	JR	Z,CBS_VD_MATCH_FRAGMENT
	LD	(CBS_VD_HMMM_NY),A		; high byte remains zero
	LD	B,00h			; BC = complete-row count for Y advance
	LD	DE,(CBS_VD_REMAIN)
	EX	DE,HL			; HL=remaining, DE=trailing bytes
	OR	A
	SBC	HL,DE			; HL=packed bytes advanced by full rows
	LD	(CBS_VD_MATCH_ROW_ADVANCE),HL
	LD	HL,(CBS_VD_WIDTH)
	LD	(CBS_VD_CHUNK),HL		; HMMM NX is one complete row
	PUSH	BC			; preserve row count across command setup
	CALL	CBS_VD_START_HMMM
	POP	DE
	LD	HL,(CBS_VD_CURRENT_Y)
	ADD	HL,DE
	LD	(CBS_VD_CURRENT_Y),HL
	LD	HL,0000h
CBS_VD_MATCH_ROW_ADVANCE:	EQU	$ - 2
	LD	(CBS_VD_CHUNK),HL
	JP	CBS_VD_MATCH_SUBTRACT

CBS_VD_MATCH_FRAGMENT:
	LD	HL,0001h
	LD	(CBS_VD_HMMM_NY),HL		; one-line fragment default
	CALL	CBS_VD_CHOOSE_DEST_CHUNK
	LD	HL,(CBS_VD_WIDTH)
	LD	DE,0000h
CBS_VD_SOURCE_COLUMN:		EQU	$ - 2
	OR	A
	SBC	HL,DE
	LD	DE,(CBS_VD_CHUNK)
	CALL	CBS_VD_MIN_HL_DE
	LD	DE,0000h
CBS_VD_DISTANCE:		EQU	$ - 2
	CALL	CBS_VD_MIN_HL_DE
	LD	(CBS_VD_CHUNK),HL

	LD	A,H
	OR	A
	JR	NZ,CBS_VD_MATCH_START_HMMM
	LD	A,L
	CP	CBS_VD_TINY_MATCH_MAX+1
	JR	NC,CBS_VD_MATCH_START_HMMM

; Tiny non-overlapping fragments avoid VDP command setup. Read at most eight
; source bytes into RAM, reset the VRAM address, then write them to destination.
	CALL	CBS_VD_WAIT_IF_COMMAND
	DI				; R14 and VRAM read-address sequence is atomic
	LD	HL,0000h
CBS_VD_SOURCE_Y:		EQU	$ - 2
	LD	B,H			; selected VRAM page
	LD	H,00h			; Y within page
CBS_VD_TINY_SCREEN_LINE_SHIFT:
	LD	A,07h			; patched by CBS_INIT_SCREEN

CBS_VD_TINY_LINE_SHIFT:
	ADD	HL,HL
	DEC	A
	JR	NZ,CBS_VD_TINY_LINE_SHIFT
	LD	DE,(CBS_VD_START_X)
	ADD	HL,DE
	LD	DE,(CBS_VD_SOURCE_COLUMN)
	ADD	HL,DE

	PUSH	HL
	LD	A,H
	AND	C0h
	RLCA
	RLCA
	LD	C,A
	LD	A,B
CBS_VD_TINY_SCREEN_PAGE_SHIFT:
	LD	B,01h			; patched by CBS_INIT_SCREEN

CBS_VD_TINY_PAGE_SHIFT:
	ADD	A,A
	DJNZ	CBS_VD_TINY_PAGE_SHIFT
	OR	C
CBS_VD_PORT1_TINY_R14_VALUE:
	OUT	(99h),A
	LD	A,8Eh
CBS_VD_PORT1_TINY_R14_SELECT:
	OUT	(99h),A
	POP	HL
	LD	A,L
CBS_VD_PORT1_TINY_ADDRESS_LOW:
	OUT	(99h),A
	LD	A,H
	AND	3Fh
CBS_VD_PORT1_TINY_ADDRESS_HIGH:
	OUT	(99h),A
	EI

	LD	HL,CBS_VD_TINY_BUFFER
	LD	A,(CBS_VD_CHUNK)
	LD	B,A
CBS_VD_PORT0_TINY_READ:
	LD	C,98h
	INIR
	CALL	CBS_VD_SET_WRITE_ADDRESS
	LD	HL,CBS_VD_TINY_BUFFER
	LD	A,(CBS_VD_CHUNK)
	LD	B,A
CBS_VD_PORT0_TINY_WRITE:
	LD	C,98h
	OTIR
	JR	CBS_VD_MATCH_ADVANCE

; Larger fragments build R32-R43 directly in registers. C remains the VDP
; command port, eliminating both the writable command block and its OTIR.
CBS_VD_MATCH_START_HMMM:
	CALL	CBS_VD_START_HMMM
	JR	CBS_VD_MATCH_ADVANCE

CBS_VD_START_HMMM:
	CALL	CBS_VD_WAIT_IF_COMMAND
	DI				; R17 and R32-R46 command sequence is atomic

	LD	A,20h
CBS_VD_PORT1_COMMAND_INDEX:
	OUT	(99h),A
	LD	A,91h
CBS_VD_PORT1_INDIRECT_SELECT:
	OUT	(99h),A
CBS_VD_PORT3_HMMM:
	LD	C,9Bh

	LD	HL,(CBS_VD_START_X)
	LD	DE,(CBS_VD_SOURCE_COLUMN)
	ADD	HL,DE
CBS_VD_SCREEN_SHIFT_SX:
	LD	B,01h			; patched by CBS_INIT_SCREEN
	CALL	CBS_VD_SHIFT_HL
	OUT	(C),L			; R32 SX low
	OUT	(C),H			; R33 SX high

	LD	HL,(CBS_VD_SOURCE_Y)
	OUT	(C),L			; R34 SY low
	OUT	(C),H			; R35 SY high

	LD	HL,0000h
CBS_VD_START_X:			EQU	$ - 2
	LD	DE,(CBS_VD_COLUMN)
	ADD	HL,DE
CBS_VD_SCREEN_SHIFT_DX:
	LD	B,01h			; patched by CBS_INIT_SCREEN
	CALL	CBS_VD_SHIFT_HL
	OUT	(C),L			; R36 DX low
	OUT	(C),H			; R37 DX high

	LD	HL,(CBS_VD_CURRENT_Y)
	OUT	(C),L			; R38 DY low
	OUT	(C),H			; R39 DY high

	LD	HL,(CBS_VD_CHUNK)
CBS_VD_SCREEN_SHIFT_NX:
	LD	B,01h			; patched by CBS_INIT_SCREEN
	CALL	CBS_VD_SHIFT_HL
	LD	A,H
	AND	01h			; VDP encodes NX=512 as zero
	LD	H,A
	OUT	(C),L			; R40 NX low
	OUT	(C),H			; R41 NX high
	LD	A,01h
	LD	(CBS_VD_COMMAND_ACTIVE),A
	LD	HL,0001h
CBS_VD_HMMM_NY:			EQU	$ - 2
	OUT	(C),L			; R42 NY low
	OUT	(C),H			; R43 NY high
	XOR	A
	OUT	(C),A			; R44 unused
	OUT	(C),A			; R45 forward
	LD	A,D0h
	OUT	(C),A			; R46 HMMM
	EI
	RET

CBS_VD_MATCH_ADVANCE:
	CALL	CBS_VD_ADVANCE
CBS_VD_MATCH_SUBTRACT:
	CALL	CBS_VD_SUBTRACT_CHUNK
	JP	NZ,CBS_VD_COPY_MATCH_LOOP
	RET

; -----------------------------------------------------------------------------
; Choose min(token remaining, bytes left in destination row).
; -----------------------------------------------------------------------------
CBS_VD_CHOOSE_DEST_CHUNK:
	LD	HL,(CBS_VD_WIDTH)
	LD	DE,0000h
CBS_VD_COLUMN:			EQU	$ - 2
	OR	A
	SBC	HL,DE
	LD	DE,0000h
CBS_VD_REMAIN:			EQU	$ - 2
	CALL	CBS_VD_MIN_HL_DE
	LD	(CBS_VD_CHUNK),HL
	RET

; Return min(HL,DE) in HL.
CBS_VD_MIN_HL_DE:
	PUSH	HL
	OR	A
	SBC	HL,DE
	POP	HL
	RET	C
	RET	Z
	EX	DE,HL
	RET

CBS_VD_SUBTRACT_CHUNK:
	LD	HL,(CBS_VD_REMAIN)
	LD	DE,(CBS_VD_CHUNK)
	OR	A
	SBC	HL,DE
	LD	(CBS_VD_REMAIN),HL
	LD	A,H
	OR	L
	RET

; Advance the packed destination coordinate.
CBS_VD_ADVANCE:
	LD	HL,(CBS_VD_COLUMN)
	LD	DE,(CBS_VD_CHUNK)
	ADD	HL,DE
	LD	(CBS_VD_COLUMN),HL
	LD	DE,(CBS_VD_WIDTH)
	OR	A
	SBC	HL,DE
	RET	NZ

	LD	HL,0000h
	LD	(CBS_VD_COLUMN),HL
	LD	HL,0000h
CBS_VD_CURRENT_Y:		EQU	$ - 2
	INC	HL
	LD	(CBS_VD_CURRENT_Y),HL
	RET

; -----------------------------------------------------------------------------
; Start HMMV for the destination rectangle held in two immediate operands.
; R17 starts at R36, so only DX, DY, NX and NY are emitted before R44-R46.
; -----------------------------------------------------------------------------
CBS_VD_START_HMMV:
	DI				; R17 and R36-R46 command sequence is atomic
	LD	A,24h
CBS_VD_PORT1_FILL_COMMAND_INDEX:
	OUT	(99h),A
	LD	A,91h
CBS_VD_PORT1_FILL_INDIRECT_SELECT:
	OUT	(99h),A
CBS_VD_PORT3_HMMV:
	LD	C,9Bh

	LD	HL,(CBS_VD_START_X)
	LD	DE,(CBS_VD_COLUMN)
	ADD	HL,DE
CBS_VD_HMMV_SCREEN_SHIFT_DX:
	LD	B,01h			; patched by CBS_INIT_SCREEN
	CALL	CBS_VD_SHIFT_HL
	OUT	(C),L			; R36 DX low
	OUT	(C),H			; R37 DX high

	LD	HL,(CBS_VD_CURRENT_Y)
	OUT	(C),L			; R38 DY low
	OUT	(C),H			; R39 DY high

	LD	HL,0000h
CBS_VD_HMMV_NX:			EQU	$ - 2
CBS_VD_HMMV_SCREEN_SHIFT_NX:
	LD	B,01h			; patched by CBS_INIT_SCREEN
	CALL	CBS_VD_SHIFT_HL
	LD	A,H
	AND	01h			; VDP encodes NX=512 as zero
	LD	H,A
	OUT	(C),L			; R40 NX low
	OUT	(C),H			; R41 NX high

	LD	HL,0001h
CBS_VD_HMMV_NY:			EQU	$ - 2
	OUT	(C),L			; R42 NY low
	OUT	(C),H			; R43 NY high

	LD	A,(CBS_VD_FILL_COLOR_LOAD+1)
	OUT	(C),A			; R44 packed fill byte
	XOR	A
	OUT	(C),A			; R45 forward
	LD	A,C0h
	OUT	(C),A			; R46 HMMV

	LD	A,01h
	LD	(CBS_VD_COMMAND_ACTIVE),A
	EI
	RET

CBS_VD_SHIFT_HL:
	LD	A,B
	OR	A
	RET	Z
	ADD	HL,HL
	DEC	B
	JR	CBS_VD_SHIFT_HL

; -----------------------------------------------------------------------------
; CPU VRAM write address for the current destination packed byte coordinate.
; -----------------------------------------------------------------------------
CBS_VD_SET_WRITE_ADDRESS:
	DI				; R14 and VRAM write-address sequence is atomic
	LD	HL,(CBS_VD_CURRENT_Y)
	LD	B,H			; selected VRAM page
	LD	H,00h			; Y within page
CBS_VD_SCREEN_LINE_SHIFT:
	LD	A,07h			; patched by CBS_INIT_SCREEN

CBS_VD_ADDRESS_LINE_SHIFT:
	ADD	HL,HL
	DEC	A
	JR	NZ,CBS_VD_ADDRESS_LINE_SHIFT
	LD	DE,(CBS_VD_START_X)
	ADD	HL,DE
	LD	DE,(CBS_VD_COLUMN)
	ADD	HL,DE

	PUSH	HL
	LD	A,H
	AND	C0h
	RLCA
	RLCA
	LD	C,A
	LD	A,B
CBS_VD_SCREEN_PAGE_SHIFT:
	LD	B,01h			; patched by CBS_INIT_SCREEN

CBS_VD_ADDRESS_PAGE_SHIFT:
	ADD	A,A
	DJNZ	CBS_VD_ADDRESS_PAGE_SHIFT
	OR	C
CBS_VD_PORT1_R14_VALUE:
	OUT	(99h),A
	LD	A,8Eh
CBS_VD_PORT1_R14_SELECT:
	OUT	(99h),A			; write R14
	POP	HL
	LD	A,L
CBS_VD_PORT1_ADDRESS_LOW:
	OUT	(99h),A
	LD	A,H
	AND	3Fh
	OR	40h
CBS_VD_PORT1_ADDRESS_HIGH:
	OUT	(99h),A			; CPU VRAM write
	EI
	RET

; -----------------------------------------------------------------------------
; Interlaced Elias readers used by the v5.5 row parser.
; -----------------------------------------------------------------------------
CBS_VD_ELIAS:
	LD	BC,0001h			; VDP writers do not leave BC at zero
	JR	CBS_VD_ELIAS_LOOP

CBS_VD_GET_BIT:
	ADD	A,A
	RET	NZ
	LD	A,(HL)
	INC	HL
	RLA
	RET

CBS_VD_ELIAS_LOOP:
	ADD	A,A
	JR	NZ,CBS_VD_ELIAS_SKIP
	LD	A,(HL)
	INC	HL
	RLA

CBS_VD_ELIAS_SKIP:
	RET	C

CBS_VD_ELIAS_BACKTRACK:
	ADD	A,A
	JR	NZ,CBS_VD_ELIAS_BACKTRACK_READY
	LD	A,(HL)
	INC	HL
	RLA
CBS_VD_ELIAS_BACKTRACK_READY:
	RL	C
	RL	B
	JR	CBS_VD_ELIAS_LOOP

; A = Screen mode 5-8. The trusted caller supplies a valid mode. Each table row
; is pixel shift, VRAM line shift and page-bank shift respectively.
CBS_VD_TINY_BUFFER:
	DS	CBS_VD_TINY_MATCH_MAX,00h

; -----------------------------------------------------------------------------
; One-time machine initializer. VDP.DW at 0007h supplies the write base port;
; VDP.DR at 0006h independently supplies the read base port. Call CBS_INIT_MSX
; once before the first decrunch. It remains intact after every image.
; -----------------------------------------------------------------------------

CBT_TILE_DECRUNCH:
	LD	(CBT_WRAPPER),HL
	LD	(CBT_DEST_PACKED),DE
	LD	(CBT_DEST_FLAGS),A
	AND	03h
	JP	NZ,CBT_UNSUPPORTED
	LD	A,D
	ADD	A,A			; Screen 5 packed X to pixels
	LD	(CBT_BASE_X),A
	XOR	A
	LD	(CBT_BASE_X+1),A
	LD	A,E
	LD	(CBT_BASE_Y),A
	LD	A,(CBT_DEST_FLAGS)
	AND	0Ch
	RRCA
	RRCA
	LD	(CBT_BASE_Y+1),A
	CP	03h
	LD	A,0Ch			; page 3 scratch unless page 3 is output
	JR	NZ,CBT_SCRATCH_PAGE_READY
	LD	A,08h			; page 2 scratch for page 3 output
CBT_SCRATCH_PAGE_READY:
	LD	(CBT_SCRATCH_FLAGS),A
	RRCA
	RRCA
	ADD	A,A			; Screen 5 page number * two R14 banks
	LD	(CBT_SCRATCH_R14_BASE),A
	LD	DE,0004h
	ADD	HL,DE
	LD	A,(HL)
	CP	05h
	JP	NZ,CBT_UNSUPPORTED
	INC	HL
	LD	A,(HL)
	OR	A
	JP	Z,CBT_UNSUPPORTED
	CP	21h			; 1-32 tile columns
	JP	NC,CBT_UNSUPPORTED
	LD	(CBT_TILE_COLUMNS),A
	INC	HL
	LD	A,(HL)
	OR	A
	JP	Z,CBT_UNSUPPORTED
	CP	21h			; 1-32 tile rows
	JP	NC,CBT_UNSUPPORTED
	LD	(CBT_TILE_ROWS),A
	LD	B,A
	INC	HL
	LD	A,(HL)			; prefix tile rows
	OR	A
	JP	Z,CBT_UNSUPPORTED
	CP	B
	JR	C,CBT_PREFIX_GEOMETRY_READY
	JR	Z,CBT_PREFIX_GEOMETRY_READY
	JP	CBT_UNSUPPORTED
CBT_PREFIX_GEOMETRY_READY:
	LD	(CBT_PREFIX_TILE_ROWS),A
	LD	A,(CBT_TILE_COLUMNS)
	LD	E,A
	LD	D,00h
	LD	A,(CBT_TILE_ROWS)
	LD	B,A
	LD	HL,0000h
CBT_TOTAL_CELLS_LOOP:
	ADD	HL,DE
	DJNZ	CBT_TOTAL_CELLS_LOOP
	LD	(CBT_TOTAL_CELLS),HL

	LD	HL,(CBT_WRAPPER)
	LD	DE,000Ch
	ADD	HL,DE			; nested C5 prefix
	LD	DE,(CBT_DEST_PACKED)
	LD	A,(CBT_DEST_FLAGS)
	CALL	CBS_V5354_DIRECT_VRAM

	LD	HL,(CBT_WRAPPER)
	LD	DE,0008h
	ADD	HL,DE
	LD	E,(HL)
	INC	HL
	LD	D,(HL)			; packed prefix length
	LD	HL,(CBT_WRAPPER)
	ADD	HL,DE
	LD	DE,000Ch
	ADD	HL,DE			; nested C5 command stream
	LD	DE,0000h
	LD	A,(CBT_SCRATCH_FLAGS)
	CALL	CBS_V5354_DIRECT_VRAM
	CALL	CBS_VD_WAIT_COMMAND

	LD	HL,0000h
	LD	(CBT_COMMAND_OFFSET),HL
	LD	A,(CBT_PREFIX_TILE_ROWS)
	LD	B,A
	LD	A,(CBT_TILE_COLUMNS)
	LD	E,A
	LD	D,00h
	LD	HL,0000h
CBT_PREFIX_TILE_COUNT:
	ADD	HL,DE
	DJNZ	CBT_PREFIX_TILE_COUNT
	DEC	HL
	LD	(CBT_DESTINATION_INDEX),HL

CBT_COMMAND_LOOP:
	CALL	CBT_READ_COMMAND
	CP	0FFh
	JP	Z,CBT_FINISHED
	LD	(CBT_OPCODE),A
	OR	A
	JR	Z,CBT_LITERAL
	BIT	7,A
	JP	Z,CBT_UNSUPPORTED
	BIT	6,A
	JR	NZ,CBT_EXTENDED_SHAPE
	AND	0Fh
	ADD	A,A
	LD	E,A
	LD	D,00h
	LD	HL,CBT_SHAPES
	ADD	HL,DE
	LD	A,(HL)
	LD	(CBT_WIDTH_CELLS),A
	INC	HL
	LD	A,(HL)
	LD	(CBT_HEIGHT_CELLS),A
	JR	CBT_REFERENCE_FIELDS

CBT_EXTENDED_SHAPE:
	CALL	CBT_READ_COMMAND
	LD	B,A
	RRCA
	RRCA
	RRCA
	RRCA
	AND	0Fh
	INC	A
	LD	(CBT_WIDTH_CELLS),A
	LD	A,B
	AND	0Fh
	INC	A
	LD	(CBT_HEIGHT_CELLS),A

CBT_REFERENCE_FIELDS:
	CALL	CBT_READ_COMMAND
	LD	E,A
	CALL	CBT_READ_COMMAND
	LD	D,A
	LD	A,D
	AND	03h
	LD	H,A
	LD	L,E
	LD	(CBT_SOURCE_INDEX),HL
	LD	A,D
	SRL	A
	SRL	A
	CP	3Fh
	JR	NZ,CBT_REFERENCE_DELTA_READY
	CALL	CBT_READ_ULEB
	JR	CBT_REFERENCE_ADD_DELTA
CBT_REFERENCE_DELTA_READY:
	INC	A
	LD	L,A
	LD	H,00h
CBT_REFERENCE_ADD_DELTA:
	CALL	CBT_ADD_DESTINATION
	LD	HL,(CBT_DESTINATION_INDEX)
	CALL	CBT_VALIDATE_INDEX
	JP	NC,CBT_UNSUPPORTED
	LD	HL,(CBT_SOURCE_INDEX)
	CALL	CBT_VALIDATE_INDEX
	JP	NC,CBT_UNSUPPORTED
	CALL	CBT_PREPARE_RECTANGLE
	LD	A,(CBT_OPCODE)
	AND	30h
	JR	Z,CBT_COPY_EXACT
	CP	20h
	JR	Z,CBT_COPY_VERTICAL
	CALL	CBT_COPY_HORIZONTAL
	JP	CBT_COMMAND_LOOP

CBT_LITERAL:
	CALL	CBT_READ_ULEB
	CALL	CBT_ADD_DESTINATION
	LD	A,01h
	LD	(CBT_WIDTH_CELLS),A
	LD	(CBT_HEIGHT_CELLS),A
	LD	HL,(CBT_DESTINATION_INDEX)
	CALL	CBT_VALIDATE_INDEX
	JP	NC,CBT_UNSUPPORTED
	CALL	CBT_PREPARE_DESTINATION
	LD	A,08h
	LD	(CBT_ROW_COUNT),A
CBT_LITERAL_ROW:
	LD	HL,CBT_ROW_BUFFER
	LD	B,04h
CBT_LITERAL_READ:
	CALL	CBT_READ_COMMAND
	LD	(HL),A
	INC	HL
	DJNZ	CBT_LITERAL_READ
	CALL	CBT_SET_DEST_WRITE
	LD	HL,CBT_ROW_BUFFER
	LD	B,04h
	LD	A,(CBT_PORT_WRITE)
	LD	C,A
	OTIR
	CALL	CBT_NEXT_DEST_ROW
	LD	HL,CBT_ROW_COUNT
	DEC	(HL)
	JR	NZ,CBT_LITERAL_ROW
	JP	CBT_COMMAND_LOOP

CBT_COPY_EXACT:
	LD	A,(CBT_HEIGHT_PIXELS)
	LD	(CBT_COPY_HEIGHT),A
	CALL	CBT_START_HMMM
	JP	CBT_COMMAND_LOOP

CBT_COPY_VERTICAL:
	LD	A,(CBT_HEIGHT_PIXELS)
	LD	(CBT_ROW_COUNT),A
	DEC	A
	LD	E,A
	LD	D,00h
	LD	HL,(CBT_SOURCE_Y)
	ADD	HL,DE
	LD	(CBT_SOURCE_Y),HL
	LD	A,01h
	LD	(CBT_COPY_HEIGHT),A
CBT_VERTICAL_ROW:
	CALL	CBT_START_HMMM
	CALL	CBS_VD_WAIT_COMMAND
	CALL	CBT_PREVIOUS_SOURCE_ROW
	CALL	CBT_NEXT_DEST_ROW
	LD	HL,CBT_ROW_COUNT
	DEC	(HL)
	JR	NZ,CBT_VERTICAL_ROW
	JP	CBT_COMMAND_LOOP

CBT_COPY_HORIZONTAL:
	LD	A,(CBT_HEIGHT_PIXELS)
	LD	(CBT_ROW_COUNT),A
	LD	A,(CBT_OPCODE)
	AND	20h
	JR	Z,CBT_HORIZONTAL_ROW
	LD	A,(CBT_HEIGHT_PIXELS)
	DEC	A
	LD	E,A
	LD	D,00h
	LD	HL,(CBT_SOURCE_Y)
	ADD	HL,DE
	LD	(CBT_SOURCE_Y),HL
CBT_HORIZONTAL_ROW:
	CALL	CBT_SET_SOURCE_READ
	LD	HL,CBT_ROW_BUFFER
	LD	A,(CBT_WIDTH_BYTES)
	LD	B,A
	LD	A,(CBT_PORT_READ)
	LD	C,A
	INIR
	CALL	CBT_SET_DEST_WRITE
	LD	A,(CBT_WIDTH_BYTES)
	LD	B,A
	LD	E,A
	LD	D,00h
	LD	HL,CBT_ROW_BUFFER
	ADD	HL,DE
	DEC	HL
	LD	A,(CBT_PORT_WRITE)
	LD	C,A
CBT_HORIZONTAL_WRITE:
	LD	A,(HL)
	RRCA
	RRCA
	RRCA
	RRCA			; reverse the two Screen 5 pixels in this byte
	OUT	(C),A
	DEC	HL
	DJNZ	CBT_HORIZONTAL_WRITE
	LD	A,(CBT_OPCODE)
	AND	20h
	JR	Z,CBT_HORIZONTAL_SOURCE_FORWARD
	CALL	CBT_PREVIOUS_SOURCE_ROW
	JR	CBT_HORIZONTAL_DESTINATION
CBT_HORIZONTAL_SOURCE_FORWARD:
	CALL	CBT_NEXT_SOURCE_ROW
CBT_HORIZONTAL_DESTINATION:
	CALL	CBT_NEXT_DEST_ROW
	LD	HL,CBT_ROW_COUNT
	DEC	(HL)
	JR	NZ,CBT_HORIZONTAL_ROW
	JP	CBT_COMMAND_LOOP

CBT_FINISHED:
	CALL	CBS_VD_WAIT_COMMAND
	OR	A
	RET
CBT_UNSUPPORTED:
	SCF
	RET

; HL = unsigned destination delta.
CBT_ADD_DESTINATION:
	LD	DE,(CBT_DESTINATION_INDEX)
	ADD	HL,DE
	LD	(CBT_DESTINATION_INDEX),HL
	RET

; HL = tile index. Validate the index and the current rectangle shape against
; both right and bottom edges. Carry set means safe; Carry clear rejects it.
CBT_VALIDATE_INDEX:
	LD	DE,(CBT_TOTAL_CELLS)
	OR	A
	SBC	HL,DE
	RET	NC
	ADD	HL,DE
	LD	A,(CBT_TILE_COLUMNS)
	LD	E,A
	LD	D,00h
	LD	C,00h
CBT_VALIDATE_INDEX_ROW:
	OR	A
	SBC	HL,DE
	JR	C,CBT_VALIDATE_INDEX_REMAINDER
	INC	C
	JR	CBT_VALIDATE_INDEX_ROW
CBT_VALIDATE_INDEX_REMAINDER:
	ADD	HL,DE
	LD	A,(CBT_WIDTH_CELLS)
	ADD	A,L
	LD	B,A
	LD	A,(CBT_TILE_COLUMNS)
	CP	B
	JR	C,CBT_VALIDATE_INDEX_BAD
	LD	A,(CBT_HEIGHT_CELLS)
	ADD	A,C
	LD	B,A
	LD	A,(CBT_TILE_ROWS)
	CP	B
	JR	C,CBT_VALIDATE_INDEX_BAD
	SCF
	RET
CBT_VALIDATE_INDEX_BAD:
	OR	A
	RET

; CBT1 full-screen source IDs and destination IDs are 0..1023.
CBT_PREPARE_RECTANGLE:
	LD	HL,(CBT_SOURCE_INDEX)
	CALL	CBT_INDEX_TO_COORDINATE
	PUSH	HL
	EX	DE,HL
	LD	DE,(CBT_BASE_X)
	ADD	HL,DE
	LD	(CBT_SOURCE_X),HL
	POP	HL
	LD	DE,(CBT_BASE_Y)
	ADD	HL,DE
	LD	(CBT_SOURCE_Y),HL
CBT_PREPARE_DESTINATION:
	LD	HL,(CBT_DESTINATION_INDEX)
	CALL	CBT_INDEX_TO_COORDINATE
	PUSH	HL
	EX	DE,HL
	LD	DE,(CBT_BASE_X)
	ADD	HL,DE
	LD	(CBT_DEST_X),HL
	POP	HL
	LD	DE,(CBT_BASE_Y)
	ADD	HL,DE
	LD	(CBT_DEST_Y),HL
	LD	A,(CBT_WIDTH_CELLS)
	ADD	A,A
	ADD	A,A
	LD	(CBT_WIDTH_BYTES),A
	ADD	A,A
	LD	(CBT_WIDTH_PIXELS),A
	LD	A,(CBT_HEIGHT_CELLS)
	ADD	A,A
	ADD	A,A
	ADD	A,A
	LD	(CBT_HEIGHT_PIXELS),A
	RET

; HL=raster tile index. Return DE=X pixels and HL=Y pixels. The stored tile
; column count avoids assuming or deriving a 32-tile-wide screen.
CBT_INDEX_TO_COORDINATE:
	LD	A,(CBT_TILE_COLUMNS)
	LD	E,A
	LD	D,00h
	LD	C,00h			; quotient = tile Y
CBT_INDEX_DIVIDE_ROW:
	OR	A
	SBC	HL,DE
	JR	C,CBT_INDEX_REMAINDER
	INC	C
	JR	CBT_INDEX_DIVIDE_ROW
CBT_INDEX_REMAINDER:
	ADD	HL,DE			; remainder = tile X
	LD	A,L
	ADD	A,A
	ADD	A,A
	ADD	A,A
	LD	E,A
	LD	D,00h
	LD	A,C
	ADD	A,A
	ADD	A,A
	ADD	A,A
	LD	L,A
	LD	H,00h
	RET

CBT_NEXT_SOURCE_ROW:
	LD	HL,(CBT_SOURCE_Y)
	INC	HL
	LD	(CBT_SOURCE_Y),HL
	RET
CBT_PREVIOUS_SOURCE_ROW:
	LD	HL,(CBT_SOURCE_Y)
	DEC	HL
	LD	(CBT_SOURCE_Y),HL
	RET
CBT_NEXT_DEST_ROW:
	LD	HL,(CBT_DEST_Y)
	INC	HL
	LD	(CBT_DEST_Y),HL
	RET

; Two-byte ULEB is sufficient for the 1,024-cell Screen 5 raster.
CBT_READ_ULEB:
	CALL	CBT_READ_COMMAND
	LD	B,A
	AND	7Fh
	LD	L,A
	LD	H,00h
	BIT	7,B
	RET	Z
	PUSH	HL
	CALL	CBT_READ_COMMAND
	AND	7Fh
	LD	L,A
	LD	H,00h
	LD	B,07h
CBT_ULEB_SHIFT:
	ADD	HL,HL
	DJNZ	CBT_ULEB_SHIFT
	POP	DE
	ADD	HL,DE
	RET

; Read one expanded command byte from Screen 5 page 3 and advance its offset.
; All general registers except AF are preserved.
CBT_READ_COMMAND:
	PUSH	BC
	PUSH	DE
	PUSH	HL
	CALL	CBS_VD_WAIT_COMMAND
	DI
	LD	HL,(CBT_COMMAND_OFFSET)
	PUSH	HL
	LD	A,H
	AND	0C0h
	RLCA
	RLCA
	LD	B,A
	LD	A,(CBT_SCRATCH_R14_BASE)
	ADD	A,B
	CALL	CBT_SET_R14
	POP	HL
	CALL	CBT_SET_READ_ADDRESS
	EI
	LD	A,(CBT_PORT_READ)
	LD	C,A
	IN	A,(C)
	PUSH	AF
	LD	HL,(CBT_COMMAND_OFFSET)
	INC	HL
	LD	(CBT_COMMAND_OFFSET),HL
	POP	AF
	POP	HL
	POP	DE
	POP	BC
	RET

CBT_SET_SOURCE_READ:
	CALL	CBS_VD_WAIT_COMMAND
	DI
	LD	HL,(CBT_SOURCE_Y)
	CALL	CBT_SCREEN5_ROW_ADDRESS
	LD	DE,(CBT_SOURCE_X)
	SRL	D
	RR	E
	ADD	HL,DE
	PUSH	HL
	LD	A,H
	AND	0C0h
	RLCA
	RLCA
	CALL	CBT_SET_R14
	POP	HL
	CALL	CBT_SET_READ_ADDRESS
	EI
	RET

CBT_SET_DEST_WRITE:
	CALL	CBS_VD_WAIT_COMMAND
	DI
	LD	HL,(CBT_DEST_Y)
	CALL	CBT_SCREEN5_ROW_ADDRESS
	LD	DE,(CBT_DEST_X)
	SRL	D
	RR	E
	ADD	HL,DE
	PUSH	HL
	LD	A,H
	AND	0C0h
	RLCA
	RLCA
	CALL	CBT_SET_R14
	POP	HL
	LD	A,L
	CALL	CBT_CONTROL_OUT
	LD	A,H
	AND	3Fh
	OR	40h
	CALL	CBT_CONTROL_OUT
	EI
	RET

CBT_SCREEN5_ROW_ADDRESS:
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL
	RET

CBT_SET_R14:
	CALL	CBT_CONTROL_OUT
	LD	A,8Eh
	JP	CBT_CONTROL_OUT
CBT_SET_READ_ADDRESS:
	LD	A,L
	CALL	CBT_CONTROL_OUT
	LD	A,H
	AND	3Fh
	JP	CBT_CONTROL_OUT
CBT_CONTROL_OUT:
	PUSH	BC
	LD	B,A
	LD	A,(CBT_PORT_CONTROL)
	LD	C,A
	LD	A,B
	OUT	(C),A
	POP	BC
	RET

CBT_START_HMMM:
	CALL	CBS_VD_WAIT_COMMAND
	DI
	LD	A,20h
	CALL	CBT_CONTROL_OUT
	LD	A,91h
	CALL	CBT_CONTROL_OUT
	LD	A,(CBT_PORT_COMMAND)
	LD	C,A
	LD	HL,(CBT_SOURCE_X)
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBT_SOURCE_Y)
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBT_DEST_X)
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBT_DEST_Y)
	OUT	(C),L
	OUT	(C),H
	LD	A,(CBT_WIDTH_PIXELS)
	OUT	(C),A
	XOR	A
	OUT	(C),A
	LD	A,(CBT_COPY_HEIGHT)
	OUT	(C),A
	XOR	A
	OUT	(C),A
	OUT	(C),A			; R44 unused
	OUT	(C),A			; R45 forward
	LD	A,0D0h
	OUT	(C),A			; R46 HMMM
	EI
	RET

CBT_SHAPES:
	DB	4,4, 4,2, 2,4, 3,2, 2,3, 2,2, 4,1
	DB	1,4, 3,1, 1,3, 2,1, 1,2, 1,1

CBT_WRAPPER:		DW	0000h
CBT_DEST_PACKED:	DW	0000h
CBT_BASE_X:		DW	0000h
CBT_BASE_Y:		DW	0000h
CBT_COMMAND_OFFSET:	DW	0000h
CBT_DESTINATION_INDEX:	DW	0000h
CBT_SOURCE_INDEX:	DW	0000h
CBT_TOTAL_CELLS:	DW	0000h
CBT_SOURCE_X:		DW	0000h
CBT_SOURCE_Y:		DW	0000h
CBT_DEST_X:		DW	0000h
CBT_DEST_Y:		DW	0000h
CBT_OPCODE:		DB	00h
CBT_DEST_FLAGS:		DB	00h
CBT_SCRATCH_FLAGS:	DB	00h
CBT_SCRATCH_R14_BASE:	DB	00h
CBT_TILE_COLUMNS:	DB	00h
CBT_TILE_ROWS:		DB	00h
CBT_PREFIX_TILE_ROWS:	DB	00h
CBT_WIDTH_CELLS:	DB	00h
CBT_HEIGHT_CELLS:	DB	00h
CBT_WIDTH_BYTES:	DB	00h
CBT_WIDTH_PIXELS:	DB	00h
CBT_HEIGHT_PIXELS:	DB	00h
CBT_COPY_HEIGHT:	DB	00h
CBT_ROW_COUNT:		DB	00h
CBT_PORT_READ:		DB	98h
CBT_PORT_WRITE:		DB	98h
CBT_PORT_CONTROL:	DB	99h
CBT_PORT_COMMAND:	DB	9Bh
CBT_ROW_BUFFER:		DS	20h,00h

CBT_RAM_END:

CBS_VD_INIT_MSX:
	LD	A,(0007h)
	LD	(CBS_VD_PORT0_LITERAL+1),A
	LD	(CBS_VD_PORT0_SHORT_FILL+1),A
	LD	(CBS_VD_PORT0_TINY_WRITE+1),A
	INC	A
	LD	(CBS_VD_PORT1_COMMAND_INDEX+1),A
	LD	(CBS_VD_PORT1_INDIRECT_SELECT+1),A
	LD	(CBS_VD_PORT1_R14_VALUE+1),A
	LD	(CBS_VD_PORT1_R14_SELECT+1),A
	LD	(CBS_VD_PORT1_ADDRESS_LOW+1),A
	LD	(CBS_VD_PORT1_ADDRESS_HIGH+1),A
	LD	(CBS_VD_PORT1_FILL_COMMAND_INDEX+1),A
	LD	(CBS_VD_PORT1_FILL_INDIRECT_SELECT+1),A
	LD	(CBS_VD_PORT1_OFFSET1_R14_VALUE+1),A
	LD	(CBS_VD_PORT1_OFFSET1_R14_SELECT+1),A
	LD	(CBS_VD_PORT1_OFFSET1_ADDRESS_LOW+1),A
	LD	(CBS_VD_PORT1_OFFSET1_ADDRESS_HIGH+1),A
	LD	(CBS_VD_PORT1_TINY_R14_VALUE+1),A
	LD	(CBS_VD_PORT1_TINY_R14_SELECT+1),A
	LD	(CBS_VD_PORT1_TINY_ADDRESS_LOW+1),A
	LD	(CBS_VD_PORT1_TINY_ADDRESS_HIGH+1),A
	INC	A
	INC	A
	LD	(CBS_VD_PORT3_HMMM+1),A
	LD	(CBS_VD_PORT3_HMMV+1),A

	LD	A,(0006h)
	LD	(CBS_VD_PORT0_OFFSET1_READ+1),A
	LD	(CBS_VD_PORT0_TINY_READ+1),A
	LD	A,(0006h)
	LD	(CBT_PORT_READ),A
	LD	A,(0007h)
	LD	(CBT_PORT_WRITE),A
	INC	A
	LD	(CBT_PORT_CONTROL),A
	INC	A
	INC	A
	LD	(CBT_PORT_COMMAND),A
	RET
