; =============================================================================
; CBS Cruncher v5.6 / CBM1 - 16 KB rolling output buffer
; =============================================================================
; Horizontal literals and reachable matches accumulate in RAM; older
; matches, fills, empty regions and mirror reconstruction remain in VRAM.
; The 16 KB build requires writable RAM mapped at 8000h-BFFFh.

	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    v5.6 CBM1 Screen 5 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		CBM_MIRROR_DECRUNCH
CBS_WAIT_VDP:
	JP		CBS_VD_WAIT_COMMAND

CBS_V5354_DIRECT_VRAM:
	LD	IY,0000h			; zero means an ordinary one-strip command
	LD	(CBS_VD_CURRENT_Y),IY
	LD	(CBS_VD_COLUMN),IY

	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 logical width means 256 bytes
CBS_VD_WIDTH_READY:
	LD	(CBS_VD_WIDTH),HL	; bytes in one vertical packed-byte column
	POP	HL
	LD	A,(HL)			; logical column count (packed image width)
	INC	HL			; HL = payload
	PUSH	HL			; preserve packed payload pointer
	LD	L,A
	LD	H,00h
	OR	A
	JR		NZ,CBS_VD_ROWS_READY
	INC	H
CBS_VD_ROWS_READY:
	LD	(CBS_VD_LOGICAL_ROWS),HL
	POP	HL
	XOR	A
	LD	(CBS_VD_COMMAND_ACTIVE),A
	LD	(CBS_VD_PENDING),A
	LD	(CBS_VD_PENDING+1),A
	LD	BC,FFFFh
	CALL	CBS_VD_STORE_OFFSET
	LD	A,80h
	EI
	JP		CBS_VD_ROW_START

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_BUFFERED_MATCH
	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
	PUSH	HL
	LD	HL,0000h
	OR	A
	SBC	HL,BC
	LD	(CBS_VD_DISTANCE_BYTES),HL
	POP	HL
	POP	HL
	POP	AF
	PUSH	HL
	PUSH	AF
	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:
	CALL	CBS_VD_FLUSH_PENDING
; -----------------------------------------------------------------------------
; 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

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
	CALL	CBS_VD_SELECT_MATCH_ROUTE
	LD	HL,0000h
	OR	A
	SBC	HL,BC
	LD	(CBS_VD_DISTANCE_BYTES),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.
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
	POP	AF
	POP	HL
	RET

; Select the one-byte repeat shortcut directly from signed BC. This removes
; the old positive-distance word from permanent decoder RAM.
CBS_VD_SELECT_MATCH_ROUTE:
	PUSH	BC
	INC	BC			; FFFFh (-1) becomes zero
	LD	A,B
	OR	C
	LD	DE,CBS_VD_COPY_OFFSET1
	JR		Z,CBS_VD_STORE_MATCH_ROUTE
	LD	DE,CBS_VD_COPY_MATCH_LOOP
CBS_VD_STORE_MATCH_ROUTE:
	LD	(CBS_VD_FALLBACK_MATCH_CALL+1),DE
	POP	BC
	RET

; -----------------------------------------------------------------------------
; Emit a literal run through port 98h. Each transfer ends at a destination row
; boundary.
; -----------------------------------------------------------------------------

CBS_V56_LITERAL_BUFFER:	EQU	8000h
CBS_V56_LITERAL_BUFFER_SIZE:	EQU	4000h

; Always collect the largest available literal batch before touching the VDP.
; EXX protects the input pointer only during the short size calculation. After
; LDIR, the normal stack retains the advanced pointer across interruptible VDP
; work; no live parser state remains in the alternate set.
CBS_VD_WRITE_LITERALS:
	PUSH	AF
	LD	A,00h
CBS_VD_SCAN_ORDER:	EQU	$ - 1
	CP	02h
	JP		NC,CBS_VD_WRITE_VERTICAL_LITERALS

; Horizontal traversal accumulates literals and locally satisfiable matches in
; one decoded-output batch. VRAM is touched only when the batch fills or a
; match reaches farther back than the pending RAM bytes.
CBS_VD_ACCUMULATE_LITERAL:
	PUSH	HL			; preserve packed payload pointer
	LD	HL,(CBS_VD_PENDING)
	LD	DE,CBS_V56_LITERAL_BUFFER_SIZE
	OR	A
	SBC	HL,DE
	CALL	Z,CBS_VD_FLUSH_PENDING
	LD	HL,(CBS_VD_PENDING)
	LD	A,H
	OR	L
	JR		NZ,CBS_VD_LITERAL_HAVE_START
	LD	HL,(CBS_VD_CURRENT_Y)
	LD	(CBS_VD_PENDING_Y),HL
	LD	HL,(CBS_VD_COLUMN)
	LD	(CBS_VD_PENDING_COLUMN),HL
CBS_VD_LITERAL_HAVE_START:
	POP	HL			; packed payload pointer
	PUSH	HL
	CALL	CBS_VD_CHOOSE_DEST_CHUNK
	LD	HL,CBS_V56_LITERAL_BUFFER_SIZE
	LD	DE,(CBS_VD_PENDING)
	OR	A
	SBC	HL,DE			; free bytes in accumulator
	PUSH	IX
	POP	DE
	CALL	CBS_VD_MIN_HL_DE
	PUSH	HL
	POP	IX			; IX = row-safe, capacity-safe chunk
	POP	HL			; packed payload pointer
	PUSH	HL
	LD	HL,CBS_V56_LITERAL_BUFFER
	LD	DE,(CBS_VD_PENDING)
	ADD	HL,DE
	EX	DE,HL			; DE = buffer write pointer
	POP	HL
	PUSH	IX
	POP	BC
	LDIR
	PUSH	HL			; advanced packed payload pointer
	CALL	CBS_VD_ADD_PENDING_CHUNK
	CALL	CBS_VD_ADVANCE
	CALL	CBS_VD_SUBTRACT_CHUNK
	POP	HL
	LD	DE,(CBS_VD_REMAIN)
	LD	A,D
	OR	E
	JR		NZ,CBS_VD_ACCUMULATE_LITERAL
	POP	AF
	RET

; Column traversal retains the proven immediate HMMC path. The accumulator is
; horizontal-only until a column batch can be delivered without per-byte TR
; polling.
CBS_VD_WRITE_VERTICAL_LITERALS:
CBS_VD_LITERAL_STAGE:
	EXX
	LD	HL,(CBS_VD_REMAIN)
	LD	DE,CBS_V56_LITERAL_BUFFER_SIZE
	CALL	CBS_VD_MIN_HL_DE
	LD	(CBS_VD_STAGE_REMAIN),HL
	EXX
	LD	BC,(CBS_VD_STAGE_REMAIN)
	LD	DE,CBS_V56_LITERAL_BUFFER
	LDIR
	PUSH	HL
	LD	HL,CBS_V56_LITERAL_BUFFER
CBS_VD_LITERAL_STAGE_LOOP:
	PUSH	HL
	CALL	CBS_VD_CHOOSE_DEST_CHUNK
	LD	HL,(CBS_VD_STAGE_REMAIN)
	PUSH	IX
	POP	DE
	CALL	CBS_VD_MIN_HL_DE
	PUSH	HL
	POP	IX
	POP	HL
	CALL	CBS_VD_LITERAL_OUTPUT
	PUSH	HL
	CALL	CBS_VD_ADVANCE
	CALL	CBS_VD_SUBTRACT_CHUNK
	CALL	CBS_VD_SUBTRACT_STAGE_CHUNK
	LD	IY,0000h
	POP	HL
	JR		NZ,CBS_VD_LITERAL_STAGE_LOOP
	POP	HL
	LD	DE,(CBS_VD_REMAIN)
	LD	A,D
	OR	E
	JR		NZ,CBS_VD_LITERAL_STAGE
	POP	AF
	RET

; Horizontal streams use the proven CPU VRAM data port from staged RAM. Only
; vertical streams require HMMC to turn sequential bytes into a column.
CBS_VD_LITERAL_OUTPUT:
	LD	A,(CBS_VD_SCAN_ORDER)
	CP	02h
	JP		NC,CBS_VD_LITERAL_HMMC

	PUSH	HL
	CALL	CBS_VD_WAIT_IF_COMMAND
	CALL	CBS_VD_SET_DEST_LOGICAL
	DI
	LD	HL,0000h
CBM_DEST_Y:		EQU	$ - 2
	LD	B,H
	LD	H,00h
	LD	A,07h
CBS_VD_LITERAL_CPU_LINE_SHIFT:
	ADD	HL,HL
	DEC	A
	JR		NZ,CBS_VD_LITERAL_CPU_LINE_SHIFT
	LD	DE,(CBM_DEST_X)
	SRL	D
	RR	E			; Screen 5: two pixels per packed byte
	ADD	HL,DE

	PUSH	HL
	LD	A,H
	AND	C0h
	RLCA
	RLCA
	LD	C,A
	LD	A,B
	ADD	A,A			; Screen 5 page shift
	OR	C
	OUT	(99h),A
CBM_CONTROL_PORT_0:	EQU	$ - 1
	LD	A,8Eh
	OUT	(99h),A
CBM_CONTROL_PORT_1:	EQU	$ - 1
	POP	HL
	LD	A,L
	OUT	(99h),A
CBM_CONTROL_PORT_2:	EQU	$ - 1
	LD	A,H
	AND	3Fh
	OR	40h
	OUT	(99h),A
CBM_CONTROL_PORT_3:	EQU	$ - 1

	POP	HL
	PUSH	IX
	POP	DE
	LD	B,E			; B=0 deliberately means a complete 256-byte row
	LD	A,98h
CBM_PORT_WRITE:	EQU	$ - 1
	LD	C,A
	OTIR
	EI
	RET

; HL is a packed or staged literal pointer. Return it advanced by CHUNK.
CBS_VD_LITERAL_HMMC:
	PUSH	HL
	CALL	CBS_VD_WAIT_IF_COMMAND
	CALL	CBS_VD_SET_DEST_LOGICAL
	DI
	LD	A,24h
	OUT	(99h),A
CBM_CONTROL_PORT_4:	EQU	$ - 1
	LD	A,91h
	OUT	(99h),A
CBM_CONTROL_PORT_5:	EQU	$ - 1
	LD	A,9Bh
CBM_PORT_COMMAND:	EQU	$ - 1
	LD	C,A
	LD	HL,0000h
CBM_DEST_X:		EQU	$ - 2
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBM_DEST_Y)
	OUT	(C),L
	OUT	(C),H
	CALL	CBS_VD_COMMAND_SIZE
	OUT	(C),L
	OUT	(C),H
	OUT	(C),E
	OUT	(C),D
	POP	HL
	LD	A,(HL)
	INC	HL
	OUT	(C),A
	PUSH	HL
	CALL	CBS_VD_COMMAND_ARG		; bottom-up rectangles require negative Y
	POP	HL
	OUT	(C),A
	LD	A,F0h
	OUT	(C),A
	LD	A,01h
	LD	(CBS_VD_COMMAND_ACTIVE),A
	PUSH	IX
	POP	DE
	DEC	DE
	LD	A,D
	OR	E
	JR		NZ,CBS_VD_LITERAL_HMMC_MORE
	EI
	RET
CBS_VD_LITERAL_HMMC_MORE:
	LD	A,02h
	OUT	(99h),A
CBM_CONTROL_PORT_6:	EQU	$ - 1
	LD	A,8Fh
	OUT	(99h),A		; status 2 exposes HMMC Transfer Ready
CBM_CONTROL_PORT_7:	EQU	$ - 1
	LD	A,ACh
	OUT	(99h),A
CBM_CONTROL_PORT_8:	EQU	$ - 1
	LD	A,91h
	OUT	(99h),A
CBM_CONTROL_PORT_9:	EQU	$ - 1
	LD	A,(CBM_PORT_COMMAND)
	LD	C,A
CBS_VD_LITERAL_HMMC_TRANSFER:
	CALL	CBS_VD_LITERAL_WAIT_TR
	LD	A,(HL)
	INC	HL
	OUT	(C),A
	DEC	DE
	LD	A,D
	OR	E
	JR		NZ,CBS_VD_LITERAL_HMMC_TRANSFER
	XOR	A
	OUT	(99h),A
CBM_CONTROL_PORT_10:	EQU	$ - 1
	LD	A,8Fh
	OUT	(99h),A		; restore status register 0
CBM_CONTROL_PORT_11:	EQU	$ - 1
	EI
	RET

; HMMC ignores CPU data written before status-2 bit 7 says Transfer Ready.
; Keep C as the indirect register port across the poll so the caller can emit
; the byte immediately after this helper returns.
CBS_VD_LITERAL_WAIT_TR:
	PUSH	BC
	LD	C,99h
CBM_CONTROL_PORT_26:	EQU	$ - 1
CBS_VD_LITERAL_WAIT_TR_LOOP:
	IN	A,(C)
	BIT	7,A
	JR		Z,CBS_VD_LITERAL_WAIT_TR_LOOP
	POP	BC
	RET

CBS_VD_SUBTRACT_STAGE_CHUNK:
	LD	HL,(CBS_VD_STAGE_REMAIN)
	PUSH	IX
	POP	DE
	OR	A
	SBC	HL,DE
	LD	(CBS_VD_STAGE_REMAIN),HL
	LD	A,H
	OR	L
	RET

CBS_VD_STAGE_REMAIN:	DW	0000h

CBS_VD_ADD_PENDING_CHUNK:
	LD	HL,(CBS_VD_PENDING)
	PUSH	IX
	POP	DE
	ADD	HL,DE
	LD	(CBS_VD_PENDING),HL
	RET

; Use ordinary overlapping LDIR while the match source is still inside the
; pending decoded batch. Otherwise publish the batch, then fall back to the
; existing unrestricted VRAM match backend.
CBS_VD_BUFFERED_MATCH:
	LD	A,(CBS_VD_SCAN_ORDER)
	CP	02h
	JR		NC,CBS_VD_BUFFERED_MATCH_FALLBACK
	LD	HL,(CBS_VD_PENDING)
	LD	DE,(CBS_VD_DISTANCE_BYTES)
	OR	A
	SBC	HL,DE
	JR		C,CBS_VD_BUFFERED_MATCH_FALLBACK
	LD	HL,(CBS_VD_PENDING)
	LD	DE,(CBS_VD_REMAIN)
	ADD	HL,DE
	LD	DE,CBS_V56_LITERAL_BUFFER_SIZE
	OR	A
	SBC	HL,DE
	JR		C,CBS_VD_BUFFERED_MATCH_LOCAL
	JR		Z,CBS_VD_BUFFERED_MATCH_LOCAL

CBS_VD_BUFFERED_MATCH_FALLBACK:
	CALL	CBS_VD_FLUSH_PENDING
CBS_VD_FALLBACK_MATCH_CALL:
	CALL	CBS_VD_COPY_MATCH_LOOP
	RET

CBS_VD_BUFFERED_MATCH_LOCAL:
	LD	HL,(CBS_VD_PENDING)
	LD	A,H
	OR	L
	JR		NZ,CBS_VD_BUFFERED_MATCH_LOOP
	LD	HL,(CBS_VD_CURRENT_Y)
	LD	(CBS_VD_PENDING_Y),HL
	LD	HL,(CBS_VD_COLUMN)
	LD	(CBS_VD_PENDING_COLUMN),HL
CBS_VD_BUFFERED_MATCH_LOOP:
	CALL	CBS_VD_CHOOSE_DEST_CHUNK
	LD	HL,CBS_V56_LITERAL_BUFFER
	LD	DE,(CBS_VD_PENDING)
	ADD	HL,DE			; HL = pending output end
	PUSH	HL
	LD	DE,(CBS_VD_DISTANCE_BYTES)
	OR	A
	SBC	HL,DE			; HL = source end-distance
	POP	DE			; DE = pending output end
	PUSH	IX
	POP	BC
	LDIR				; overlap deliberately reproduces LZ bytes
	CALL	CBS_VD_ADD_PENDING_CHUNK
	CALL	CBS_VD_ADVANCE
	CALL	CBS_VD_SUBTRACT_CHUNK
	LD	HL,(CBS_VD_REMAIN)
	LD	A,H
	OR	L
	JR		NZ,CBS_VD_BUFFERED_MATCH_LOOP
	RET

; Upload one accumulated horizontal batch. Save the logical end coordinate,
; replay the batch from its start in row-safe pieces, then resume parsing at
; the saved end. The packed token length is preserved across this operation.
CBS_VD_FLUSH_PENDING:
	LD	A,(CBS_VD_SCAN_ORDER)
	CP	02h
	RET	NC
	LD	HL,(CBS_VD_PENDING)
	LD	A,H
	OR	L
	RET	Z
	LD	HL,(CBS_VD_REMAIN)
	LD	(CBS_VD_SAVED_REMAIN),HL
	LD	HL,(CBS_VD_CURRENT_Y)
	LD	(CBS_VD_END_Y),HL
	LD	HL,(CBS_VD_COLUMN)
	LD	(CBS_VD_END_COLUMN),HL
	LD	HL,(CBS_VD_PENDING_Y)
	LD	(CBS_VD_CURRENT_Y),HL
	LD	HL,(CBS_VD_PENDING_COLUMN)
	LD	(CBS_VD_COLUMN),HL
	LD	HL,(CBS_VD_PENDING)
	LD	(CBS_VD_REMAIN),HL
	LD	HL,CBS_V56_LITERAL_BUFFER
CBS_VD_FLUSH_LOOP:
	PUSH	HL			; decoded batch read pointer
	CALL	CBS_VD_CHOOSE_DEST_CHUNK
	POP	HL
	CALL	CBS_VD_LITERAL_OUTPUT
	PUSH	HL			; preserve advanced batch read pointer
	CALL	CBS_VD_ADVANCE
	CALL	CBS_VD_SUBTRACT_CHUNK
	POP	HL
	LD	DE,(CBS_VD_REMAIN)
	LD	A,D
	OR	E
	JR		NZ,CBS_VD_FLUSH_LOOP
	LD	HL,(CBS_VD_END_Y)
	LD	(CBS_VD_CURRENT_Y),HL
	LD	HL,(CBS_VD_END_COLUMN)
	LD	(CBS_VD_COLUMN),HL
	LD	HL,(CBS_VD_SAVED_REMAIN)
	LD	(CBS_VD_REMAIN),HL
	XOR	A
	LD	(CBS_VD_PENDING),A
	LD	(CBS_VD_PENDING+1),A
	RET

CBS_VD_PENDING:		DW	0000h
CBS_VD_PENDING_Y:		DW	0000h
CBS_VD_PENDING_COLUMN:	DW	0000h
CBS_VD_END_Y:			DW	0000h
CBS_VD_END_COLUMN:		DW	0000h
CBS_VD_SAVED_REMAIN:		DW	0000h
CBS_VD_DISTANCE_BYTES:	DW	0001h

CBS_VD_LOGICAL_TO_XY:
	LD	A,(CBS_VD_SCAN_ORDER)
	CP	02h
	JR		NC,CBS_VD_LOGICAL_VERTICAL

	PUSH	HL			; keep logical row while X uses the column
	LD	H,D
	LD	L,E
	ADD	HL,HL
	LD	DE,0000h
CBM_BASE_X:		EQU	$ - 2
	ADD	HL,DE
	EX	(SP),HL			; stack = physical X, HL = logical row
	LD	A,(CBS_VD_SCAN_ORDER)
	OR	A
	JR		Z,CBS_VD_LOGICAL_HORIZONTAL_Y
	EX	DE,HL
	LD	HL,0000h
CBS_VD_LOGICAL_ROWS:	EQU	$ - 2
	DEC	HL
	OR	A
	SBC	HL,DE
CBS_VD_LOGICAL_HORIZONTAL_Y:
	LD	DE,0000h
CBM_BASE_Y:		EQU	$ - 2
	ADD	HL,DE
	POP	DE			; DE = physical X
	EX	DE,HL			; HL = X, DE = Y
	RET

CBS_VD_LOGICAL_VERTICAL:
	PUSH	DE			; keep logical column while X uses the row
	CP	03h
	JR		NZ,CBS_VD_LOGICAL_VERTICAL_X
	EX	DE,HL
	LD	HL,(CBS_VD_LOGICAL_ROWS)
	DEC	HL
	OR	A
	SBC	HL,DE
CBS_VD_LOGICAL_VERTICAL_X:
	ADD	HL,HL
	LD	DE,(CBM_BASE_X)
	ADD	HL,DE
	EX	(SP),HL			; stack = physical X, HL = logical column
	LD	DE,(CBM_BASE_Y)
	ADD	HL,DE
	POP	DE
	EX	DE,HL
	RET

CBS_VD_SET_SOURCE_LOGICAL:
	CALL	CBS_VD_LOGICAL_TO_XY
	LD	(CBM_SOURCE_X),HL
	EX	DE,HL
	LD	(CBM_SOURCE_Y),HL
	RET

CBS_VD_SET_DEST_LOGICAL:
	LD	HL,(CBS_VD_CURRENT_Y)
	LD	DE,(CBS_VD_COLUMN)
	CALL	CBS_VD_LOGICAL_TO_XY
	LD	(CBM_DEST_X),HL
	EX	DE,HL
	LD	(CBM_DEST_Y),HL
	RET

; Return command NX in HL and NY in DE. Horizontal streams advance packed
; bytes across X; vertical streams advance them down Y.
CBS_VD_COMMAND_SIZE:
	PUSH	IY
	POP	HL
	LD	A,H
	OR	L
	JR		Z,CBS_VD_COMMAND_SIZE_STRIP
	LD	A,(CBS_VD_SCAN_ORDER)
	CP	02h
	JR		NC,CBS_VD_COMMAND_RECT_VERTICAL
	EX	DE,HL
	LD	HL,(CBS_VD_WIDTH)
	ADD	HL,HL
	RET
CBS_VD_COMMAND_RECT_VERTICAL:
	ADD	HL,HL
	LD	DE,(CBS_VD_WIDTH)
	RET
CBS_VD_COMMAND_SIZE_STRIP:
	PUSH	IX
	POP	HL
	LD	A,(CBS_VD_SCAN_ORDER)
	CP	02h
	JR		NC,CBS_VD_COMMAND_SIZE_VERTICAL
	ADD	HL,HL
	LD	DE,0001h
	RET
CBS_VD_COMMAND_SIZE_VERTICAL:
	EX	DE,HL
	LD	HL,0002h
	RET

CBS_VD_COMMAND_ARG:
	PUSH	IY
	POP	HL
	LD	A,H
	OR	L
	RET	Z
	LD	A,(CBS_VD_SCAN_ORDER)
	DEC	A
	JR		NZ,CBS_VD_COMMAND_ARG_X
	LD	A,08h
	RET
CBS_VD_COMMAND_ARG_X:
	SUB	02h
	JR		Z,CBS_VD_COMMAND_ARG_NEGATIVE_X
	XOR	A
	RET
CBS_VD_COMMAND_ARG_NEGATIVE_X:
	LD	A,04h
	RET

; -----------------------------------------------------------------------------
; Matches, fills, empty-mask regions and mirror reconstruction stay in VRAM.
; The buffer capacity never limits compression offsets or changes the stream.
; -----------------------------------------------------------------------------

CBS_VD_COPY_OFFSET1:
	CALL	CBS_VD_WAIT_IF_COMMAND
	LD	DE,(CBS_VD_COLUMN)
	LD	A,D
	OR	E
	JR		NZ,CBS_VD_OFFSET1_SAME_ROW
	LD	HL,(CBS_VD_CURRENT_Y)
	DEC	HL
	LD	DE,(CBS_VD_WIDTH)
	DEC	DE
	JR		CBS_VD_OFFSET1_SOURCE_READY
CBS_VD_OFFSET1_SAME_ROW:
	DEC	DE
	LD	HL,(CBS_VD_CURRENT_Y)
CBS_VD_OFFSET1_SOURCE_READY:
	CALL	CBS_VD_SET_SOURCE_LOGICAL
	CALL	CBM_SET_SOURCE_READ
	IN	A,(98h)
CBM_PORT_READ:		EQU	$ - 1
	LD	(CBS_VD_FILL_COLOR_LOAD+1),A
CBS_VD_OFFSET1_FILL_LOOP:
	CALL	CBS_VD_TRY_FULL_FILL_ROWS
	JR		C,CBS_VD_OFFSET1_FILL_RECTANGLE
	CALL	CBS_VD_CHOOSE_DEST_CHUNK
	CALL	CBS_VD_START_HMMV
	CALL	CBS_VD_ADVANCE
	CALL	CBS_VD_SUBTRACT_CHUNK
	JR		NZ,CBS_VD_OFFSET1_FILL_LOOP
	RET
CBS_VD_OFFSET1_FILL_RECTANGLE:
	CALL	CBS_VD_START_HMMV
	LD	HL,(CBS_VD_CURRENT_Y)
	PUSH	IY
	POP	DE
	ADD	HL,DE
	LD	(CBS_VD_CURRENT_Y),HL
	CALL	CBS_VD_SUBTRACT_CHUNK
	LD	IY,0000h
	JR		NZ,CBS_VD_OFFSET1_FILL_LOOP
	RET

CBS_VD_TRY_FULL_FILL_ROWS:
	LD	HL,(CBS_VD_COLUMN)
	LD	A,H
	OR	L
	RET	NZ
	LD	HL,(CBS_VD_REMAIN)
	LD	DE,(CBS_VD_WIDTH)
	LD	BC,0000h
CBS_VD_FILL_RECT_COUNT_LOOP:
	OR	A
	SBC	HL,DE
	JR		C,CBS_VD_FILL_RECT_COUNT_RESTORE
	INC	BC
	JR		CBS_VD_FILL_RECT_COUNT_LOOP
CBS_VD_FILL_RECT_COUNT_RESTORE:
	ADD	HL,DE
	LD	A,B
	OR	C
	RET	Z
	PUSH	BC
	POP	IY
	EX	DE,HL
	LD	HL,(CBS_VD_REMAIN)
	OR	A
	SBC	HL,DE
	PUSH	HL
	POP	IX
	SCF
	RET

CBS_VD_COPY_MATCH_LOOP:
	CALL	CBS_VD_TRY_FULL_MATCH_ROWS
	JR		C,CBS_VD_MATCH_RECTANGLE
	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:
	PUSH	HL			; retain source column without a RAM spill
	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
	POP	DE
	PUSH	DE
	CALL	CBS_VD_SET_SOURCE_LOGICAL

	CALL	CBS_VD_CHOOSE_DEST_CHUNK
	LD	HL,(CBS_VD_WIDTH)
	POP	DE
	OR	A
	SBC	HL,DE
	PUSH	IX
	POP	DE
	CALL	CBS_VD_MIN_HL_DE
	PUSH	HL
	POP	IX
	CALL	CBS_VD_START_HMMM
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

CBS_VD_MATCH_RECTANGLE:
	CALL	CBS_VD_START_HMMM
	LD	HL,(CBS_VD_CURRENT_Y)
	PUSH	IY
	POP	DE
	ADD	HL,DE
	LD	(CBS_VD_CURRENT_Y),HL
	CALL	CBS_VD_SUBTRACT_CHUNK
	LD	IY,0000h
	JP		NZ,CBS_VD_COPY_MATCH_LOOP
	RET

; Combine complete aligned logical rows while staying within the already
; expanded source-row distance. Carry returns one safe HMMM rectangle.
CBS_VD_TRY_FULL_MATCH_ROWS:
	LD	HL,(CBS_VD_COLUMN)
	LD	A,H
	OR	L
	RET	NZ
	LD	HL,(CBS_VD_OFFSET_COLUMNS)
	LD	A,H
	OR	L
	RET	NZ
	LD	HL,(CBS_VD_OFFSET_ROWS)
	LD	A,H
	OR	L
	RET	Z
	LD	HL,(CBS_VD_REMAIN)
	LD	DE,(CBS_VD_WIDTH)
	LD	BC,0000h
CBS_VD_RECT_COUNT_LOOP:
	OR	A
	SBC	HL,DE
	JR		C,CBS_VD_RECT_COUNT_RESTORE
	INC	BC
	PUSH	HL
	LD	HL,(CBS_VD_OFFSET_ROWS)
	OR	A
	SBC	HL,BC
	POP	HL
	JR		Z,CBS_VD_RECT_COUNT_READY
	JR		CBS_VD_RECT_COUNT_LOOP
CBS_VD_RECT_COUNT_RESTORE:
	ADD	HL,DE
CBS_VD_RECT_COUNT_READY:
	LD	A,B
	OR	C
	RET	Z
	PUSH	BC
	POP	IY
	EX	DE,HL
	LD	HL,(CBS_VD_REMAIN)
	OR	A
	SBC	HL,DE
	PUSH	HL
	POP	IX
	LD	HL,(CBS_VD_CURRENT_Y)
	LD	DE,(CBS_VD_OFFSET_ROWS)
	OR	A
	SBC	HL,DE
	LD	DE,0000h
	CALL	CBS_VD_SET_SOURCE_LOGICAL
	SCF
	RET

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
	PUSH	HL
	POP	IX
	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)
	PUSH	IX
	POP	DE
	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)
	PUSH	IX
	POP	DE
	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:
	CALL	CBS_VD_WAIT_IF_COMMAND
	CALL	CBS_VD_SET_DEST_LOGICAL
	DI
	LD	A,24h
	OUT	(99h),A
CBM_CONTROL_PORT_12:	EQU	$ - 1
	LD	A,91h
	OUT	(99h),A
CBM_CONTROL_PORT_13:	EQU	$ - 1
	LD	A,(CBM_PORT_COMMAND)
	LD	C,A
	LD	HL,(CBM_DEST_X)
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBM_DEST_Y)
	OUT	(C),L
	OUT	(C),H
	CALL	CBS_VD_COMMAND_SIZE
	OUT	(C),L
	OUT	(C),H
	OUT	(C),E
	OUT	(C),D
CBS_VD_FILL_COLOR_LOAD:
	LD	A,00h
	OUT	(C),A
	CALL	CBS_VD_COMMAND_ARG
	OUT	(C),A
	LD	A,C0h
	JR		CBS_VD_COMMAND_ACTIVE_START

CBS_VD_START_HMMM:
	CALL	CBS_VD_WAIT_IF_COMMAND
	CALL	CBS_VD_SET_DEST_LOGICAL
	DI
	LD	A,20h
	OUT	(99h),A
CBM_CONTROL_PORT_14:	EQU	$ - 1
	LD	A,91h
	OUT	(99h),A
CBM_CONTROL_PORT_15:	EQU	$ - 1
	LD	A,(CBM_PORT_COMMAND)
	LD	C,A
	LD	HL,0000h
CBM_SOURCE_X:		EQU	$ - 2
	OUT	(C),L
	OUT	(C),H
	LD	HL,0000h
CBM_SOURCE_Y:		EQU	$ - 2
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBM_DEST_X)
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBM_DEST_Y)
	OUT	(C),L
	OUT	(C),H
	CALL	CBS_VD_COMMAND_SIZE
	OUT	(C),L
	OUT	(C),H
	OUT	(C),E
	OUT	(C),D
	XOR	A
	OUT	(C),A
	CALL	CBS_VD_COMMAND_ARG
	OUT	(C),A
	LD	A,D0h
CBS_VD_COMMAND_ACTIVE_START:
	OUT	(C),A
	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_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.

; -----------------------------------------------------------------------------
; 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.
; -----------------------------------------------------------------------------

CBM_MIRROR_DECRUNCH:
	LD	(CBM_WRAPPER),HL
	LD	(CBM_DEST_PACKED),DE
	LD	(CBM_DEST_FLAGS),A
	AND	03h
	JP		NZ,CBM_UNSUPPORTED
	LD	A,D
	ADD	A,A
	LD	(CBM_BASE_X),A
	XOR	A
	LD	(CBM_BASE_X+1),A
	LD	A,E
	LD	(CBM_BASE_Y),A
	LD	A,00h
CBM_DEST_FLAGS:	EQU	$ - 1
	AND	Ch
	RRCA
	RRCA
	LD	(CBM_BASE_Y+1),A

	LD	HL,0000h
CBM_WRAPPER:		EQU	$ - 2
	LD	DE,0004h
	ADD	HL,DE
	LD	A,(HL)
	CP	05h
	JP		NZ,CBM_UNSUPPORTED
	INC	HL
	LD	A,(HL)
	OR	A
	JP		Z,CBM_UNSUPPORTED
	CP	21h
	JP		NC,CBM_UNSUPPORTED
	LD	(CBM_TILE_COLUMNS),A
	INC	HL
	LD	A,(HL)
	OR	A
	JP		Z,CBM_UNSUPPORTED
	CP	21h
	JP		NC,CBM_UNSUPPORTED
	LD	(CBM_TILE_ROWS),A
	INC	HL
	LD	A,(HL)
	CP	04h
	JP		NC,CBM_UNSUPPORTED
	LD	(CBS_VD_SCAN_ORDER),A

	LD	A,00h
CBM_TILE_COLUMNS:	EQU	$ - 1
	LD	E,A
	LD	D,00h
	LD	B,00h
CBM_TILE_ROWS:		EQU	$ - 1
	LD	HL,0000h
CBM_TOTAL_CELLS_LOOP:
	ADD	HL,DE
	DJNZ	CBM_TOTAL_CELLS_LOOP
	LD	(CBM_TOTAL_CELLS),HL

	LD	HL,(CBM_WRAPPER)
	LD	DE,000Ch
	ADD	HL,DE
	LD	DE,0000h
CBM_DEST_PACKED:	EQU	$ - 2
	LD	A,(CBM_DEST_FLAGS)
	CALL	CBS_V5354_DIRECT_VRAM
	CALL	CBS_VD_WAIT_COMMAND

	LD	HL,(CBM_WRAPPER)
	LD	DE,0008h
	ADD	HL,DE
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	LD	HL,(CBM_WRAPPER)
	ADD	HL,DE
	LD	DE,000Ch
	ADD	HL,DE
	LD	(CBM_COMMAND_POINTER),HL

CBM_COMMAND_LOOP:
	CALL	CBM_READ_COMMAND
	CP	FFh
	JP		Z,CBM_FINISHED
	LD	(CBM_OPCODE),A
	BIT	7,A
	JP		Z,CBM_UNSUPPORTED
	BIT	6,A
	JR		NZ,CBM_EXTENDED_SHAPE
	AND	Fh
	ADD	A,A
	LD	E,A
	LD	D,00h
	LD	HL,CBM_SHAPES
	ADD	HL,DE
	LD	A,(HL)
	LD	(CBM_WIDTH_CELLS),A
	INC	HL
	LD	A,(HL)
	LD	(CBM_HEIGHT_CELLS),A
	JR		CBM_REFERENCE_FIELDS

CBM_EXTENDED_SHAPE:
	CALL	CBM_READ_COMMAND
	LD	B,A
	RRCA
	RRCA
	RRCA
	RRCA
	AND	Fh
	INC	A
	LD	(CBM_WIDTH_CELLS),A
	LD	A,B
	AND	Fh
	INC	A
	LD	(CBM_HEIGHT_CELLS),A

CBM_REFERENCE_FIELDS:
	CALL	CBM_READ_WORD
	LD	(CBM_DESTINATION_INDEX),DE
	CALL	CBM_READ_WORD
	LD	(CBM_SOURCE_INDEX),DE
	LD	HL,0000h
CBM_DESTINATION_INDEX:	EQU	$ - 2
	CALL	CBM_VALIDATE_INDEX
	JP		NC,CBM_UNSUPPORTED
	LD	HL,0000h
CBM_SOURCE_INDEX:	EQU	$ - 2
	CALL	CBM_VALIDATE_INDEX
	JP		NC,CBM_UNSUPPORTED
	CALL	CBM_PREPARE_RECTANGLE
	LD	A,00h
CBM_OPCODE:		EQU	$ - 1
	AND	30h
	JR		Z,CBM_COPY_EXACT
	CP	20h
	JR		Z,CBM_COPY_VERTICAL
	JP		CBM_COPY_HORIZONTAL	; tail route: do not leak a return address

CBM_READ_WORD:
	CALL	CBM_READ_COMMAND
	LD	E,A
	CALL	CBM_READ_COMMAND
	LD	D,A
	RET

CBM_READ_COMMAND:
	PUSH	HL
	LD	HL,0000h
CBM_COMMAND_POINTER:	EQU	$ - 2
	LD	A,(HL)
	INC	HL
	LD	(CBM_COMMAND_POINTER),HL
	POP	HL
	RET


CBM_COPY_EXACT:
	LD	A,00h
CBM_HEIGHT_PIXELS:	EQU	$ - 1
	LD	(CBM_COPY_HEIGHT),A
	CALL	CBM_START_HMMM
	JP		CBM_COMMAND_LOOP

CBM_COPY_VERTICAL:
	LD	A,(CBM_HEIGHT_PIXELS)
	EXX
	LD	B,A
	EXX
	DEC	A
	LD	E,A
	LD	D,00h
	LD	HL,(CBM_SOURCE_Y)
	ADD	HL,DE
	LD	(CBM_SOURCE_Y),HL
	LD	A,01h
	LD	(CBM_COPY_HEIGHT),A
CBM_VERTICAL_ROW:
	CALL	CBM_START_HMMM
	CALL	CBM_PREVIOUS_SOURCE_ROW
	CALL	CBM_NEXT_DEST_ROW
	EXX
	DEC	B
	EXX
	JR		NZ,CBM_VERTICAL_ROW
	JP		CBM_COMMAND_LOOP

CBM_COPY_HORIZONTAL:
	LD	A,(CBM_HEIGHT_PIXELS)
	EXX
	LD	B,A
	EXX
	LD	A,(CBM_OPCODE)
	AND	20h
	JR		Z,CBM_HORIZONTAL_ROW
	LD	A,(CBM_HEIGHT_PIXELS)
	DEC	A
	LD	E,A
	LD	D,00h
	LD	HL,(CBM_SOURCE_Y)
	ADD	HL,DE
	LD	(CBM_SOURCE_Y),HL
CBM_HORIZONTAL_ROW:
	CALL	CBM_SET_SOURCE_READ
	LD	HL,CBM_ROW_BUFFER
	LD	A,00h
CBM_WIDTH_BYTES:	EQU	$ - 1
	LD	B,A
	LD	A,(CBM_PORT_READ)
	LD	C,A
	INIR
	CALL	CBM_SET_DEST_WRITE
	LD	A,(CBM_WIDTH_BYTES)
	LD	B,A
	LD	E,A
	LD	D,00h
	LD	HL,CBM_ROW_BUFFER
	ADD	HL,DE
	DEC	HL
	LD	A,(CBM_PORT_WRITE)
	LD	C,A
CBM_HORIZONTAL_WRITE:
	LD	A,(HL)
	RRCA
	RRCA
	RRCA
	RRCA			; reverse the two Screen 5 pixels in this byte
	OUT	(C),A
	DEC	HL
	DJNZ	CBM_HORIZONTAL_WRITE
	LD	A,(CBM_OPCODE)
	AND	20h
	JR		Z,CBM_HORIZONTAL_SOURCE_FORWARD
	CALL	CBM_PREVIOUS_SOURCE_ROW
	JR		CBM_HORIZONTAL_DESTINATION
CBM_HORIZONTAL_SOURCE_FORWARD:
	CALL	CBM_NEXT_SOURCE_ROW
CBM_HORIZONTAL_DESTINATION:
	CALL	CBM_NEXT_DEST_ROW
	EXX
	DEC	B
	EXX
	JR		NZ,CBM_HORIZONTAL_ROW
	JP		CBM_COMMAND_LOOP

CBM_FINISHED:
	CALL	CBS_VD_WAIT_COMMAND
	OR	A
	RET
CBM_UNSUPPORTED:
	SCF
	RET

; HL = unsigned destination delta.
CBM_ADD_DESTINATION:
	LD	DE,(CBM_DESTINATION_INDEX)
	ADD	HL,DE
	LD	(CBM_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.
CBM_VALIDATE_INDEX:
	LD	DE,0000h
CBM_TOTAL_CELLS:	EQU	$ - 2
	OR	A
	SBC	HL,DE
	RET	NC
	ADD	HL,DE
	LD	A,(CBM_TILE_COLUMNS)
	LD	E,A
	LD	D,00h
	LD	C,00h
CBM_VALIDATE_INDEX_ROW:
	OR	A
	SBC	HL,DE
	JR		C,CBM_VALIDATE_INDEX_REMAINDER
	INC	C
	JR		CBM_VALIDATE_INDEX_ROW
CBM_VALIDATE_INDEX_REMAINDER:
	ADD	HL,DE
	LD	A,00h
CBM_WIDTH_CELLS:	EQU	$ - 1
	ADD	A,L
	LD	B,A
	LD	A,(CBM_TILE_COLUMNS)
	CP	B
	JR		C,CBM_VALIDATE_INDEX_BAD
	LD	A,00h
CBM_HEIGHT_CELLS:	EQU	$ - 1
	ADD	A,C
	LD	B,A
	LD	A,(CBM_TILE_ROWS)
	CP	B
	JR		C,CBM_VALIDATE_INDEX_BAD
	SCF
	RET
CBM_VALIDATE_INDEX_BAD:
	OR	A
	RET

; CBM1 source and destination tile IDs are 0..1023.
CBM_PREPARE_RECTANGLE:
	LD	HL,(CBM_SOURCE_INDEX)
	CALL	CBM_INDEX_TO_COORDINATE
	PUSH	HL
	EX	DE,HL
	LD	DE,(CBM_BASE_X)
	ADD	HL,DE
	LD	(CBM_SOURCE_X),HL
	POP	HL
	LD	DE,(CBM_BASE_Y)
	ADD	HL,DE
	LD	(CBM_SOURCE_Y),HL
CBM_PREPARE_DESTINATION:
	LD	HL,(CBM_DESTINATION_INDEX)
	CALL	CBM_INDEX_TO_COORDINATE
	PUSH	HL
	EX	DE,HL
	LD	DE,(CBM_BASE_X)
	ADD	HL,DE
	LD	(CBM_DEST_X),HL
	POP	HL
	LD	DE,(CBM_BASE_Y)
	ADD	HL,DE
	LD	(CBM_DEST_Y),HL
	LD	A,(CBM_WIDTH_CELLS)
	ADD	A,A
	ADD	A,A
	LD	(CBM_WIDTH_BYTES),A
	ADD	A,A
	LD	(CBM_WIDTH_PIXELS),A
	LD	A,(CBM_HEIGHT_CELLS)
	ADD	A,A
	ADD	A,A
	ADD	A,A
	LD	(CBM_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.
CBM_INDEX_TO_COORDINATE:
	LD	A,(CBM_TILE_COLUMNS)
	LD	E,A
	LD	D,00h
	LD	C,00h			; quotient = tile Y
CBM_INDEX_DIVIDE_ROW:
	OR	A
	SBC	HL,DE
	JR		C,CBM_INDEX_REMAINDER
	INC	C
	JR		CBM_INDEX_DIVIDE_ROW
CBM_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

CBM_NEXT_SOURCE_ROW:
	LD	HL,(CBM_SOURCE_Y)
	INC	HL
	LD	(CBM_SOURCE_Y),HL
	RET
CBM_PREVIOUS_SOURCE_ROW:
	LD	HL,(CBM_SOURCE_Y)
	DEC	HL
	LD	(CBM_SOURCE_Y),HL
	RET
CBM_NEXT_DEST_ROW:
	LD	HL,(CBM_DEST_Y)
	INC	HL
	LD	(CBM_DEST_Y),HL
	RET

CBM_SET_SOURCE_READ:
	CALL	CBS_VD_WAIT_COMMAND
	DI
	LD	HL,(CBM_SOURCE_Y)
	CALL	CBM_SCREEN5_ROW_ADDRESS
	LD	DE,(CBM_SOURCE_X)
	SRL	D
	RR	E
	ADD	HL,DE
	PUSH	HL
	LD	A,H
	AND	C0h
	RLCA
	RLCA
	OUT	(99h),A
CBM_CONTROL_PORT_16:	EQU	$ - 1
	LD	A,8Eh
	OUT	(99h),A
CBM_CONTROL_PORT_17:	EQU	$ - 1
	POP	HL
	LD	A,L
	OUT	(99h),A
CBM_CONTROL_PORT_18:	EQU	$ - 1
	LD	A,H
	AND	3Fh
	OUT	(99h),A
CBM_CONTROL_PORT_19:	EQU	$ - 1
	EI
	RET

CBM_SET_DEST_WRITE:
	CALL	CBS_VD_WAIT_COMMAND
	DI
	LD	HL,(CBM_DEST_Y)
	CALL	CBM_SCREEN5_ROW_ADDRESS
	LD	DE,(CBM_DEST_X)
	SRL	D
	RR	E
	ADD	HL,DE
	PUSH	HL
	LD	A,H
	AND	C0h
	RLCA
	RLCA
	OUT	(99h),A
CBM_CONTROL_PORT_20:	EQU	$ - 1
	LD	A,8Eh
	OUT	(99h),A
CBM_CONTROL_PORT_21:	EQU	$ - 1
	POP	HL
	LD	A,L
	OUT	(99h),A
CBM_CONTROL_PORT_22:	EQU	$ - 1
	LD	A,H
	AND	3Fh
	OR	40h
	OUT	(99h),A
CBM_CONTROL_PORT_23:	EQU	$ - 1
	EI
	RET

CBM_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





CBM_START_HMMM:
	CALL	CBS_VD_WAIT_COMMAND
	DI
	LD	A,20h
	OUT	(99h),A
CBM_CONTROL_PORT_24:	EQU	$ - 1
	LD	A,91h
	OUT	(99h),A
CBM_CONTROL_PORT_25:	EQU	$ - 1
	LD	A,(CBM_PORT_COMMAND)
	LD	C,A
	LD	HL,(CBM_SOURCE_X)
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBM_SOURCE_Y)
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBM_DEST_X)
	OUT	(C),L
	OUT	(C),H
	LD	HL,(CBM_DEST_Y)
	OUT	(C),L
	OUT	(C),H
	LD	A,00h
CBM_WIDTH_PIXELS:	EQU	$ - 1
	OUT	(C),A
	XOR	A
	OUT	(C),A
	LD	A,00h
CBM_COPY_HEIGHT:	EQU	$ - 1
	OUT	(C),A
	XOR	A
	OUT	(C),A
	OUT	(C),A			; R44 unused
	OUT	(C),A			; R45 forward
	LD	A,D0h
	OUT	(C),A			; R46 HMMM
	EI
	RET



CBM_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

CBM_ROW_BUFFER:		DS	20h,00h

CBM_RAM_END:

CBS_VD_INIT_MSX:
	LD	A,(0007h)
	LD	(CBM_PORT_WRITE),A
	INC	A
	LD	HL,CBM_CONTROL_PORT_PATCHES
	LD	B,27
CBM_PATCH_CONTROL_PORT_LOOP:
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	INC	HL
	LD	(DE),A
	DJNZ	CBM_PATCH_CONTROL_PORT_LOOP
	INC	A
	INC	A
	LD	(CBM_PORT_COMMAND),A
	LD	A,(0006h)
	LD	(CBM_PORT_READ),A
	RET

CBM_CONTROL_PORT_PATCHES:
	DW	CBM_CONTROL_PORT_0,CBM_CONTROL_PORT_1,CBM_CONTROL_PORT_2,CBM_CONTROL_PORT_3,CBM_CONTROL_PORT_4
	DW	CBM_CONTROL_PORT_5,CBM_CONTROL_PORT_6,CBM_CONTROL_PORT_7,CBM_CONTROL_PORT_8,CBM_CONTROL_PORT_9
	DW	CBM_CONTROL_PORT_10,CBM_CONTROL_PORT_11,CBM_CONTROL_PORT_12,CBM_CONTROL_PORT_13,CBM_CONTROL_PORT_14
	DW	CBM_CONTROL_PORT_15,CBM_CONTROL_PORT_16,CBM_CONTROL_PORT_17,CBM_CONTROL_PORT_18,CBM_CONTROL_PORT_19
	DW	CBM_CONTROL_PORT_20,CBM_CONTROL_PORT_21,CBM_CONTROL_PORT_22,CBM_CONTROL_PORT_23,CBM_CONTROL_PORT_24
	DW	CBM_CONTROL_PORT_25,CBM_CONTROL_PORT_26
