; =============================================================================
; CBS Cruncher v5.2 - combined format 0 + 2 RAM decruncher v0.5
; =============================================================================
; Minimal reference implementation for the two primary forward bitstreams.
;
; Supported:
;   - format 0: 128-byte offset quantum
;   - format 2: gamma-coded distances 1-15 plus format-0 general offsets
;   - direct RAM source -> RAM destination
;   - header 00h selects format 0; header 80h selects format 2
;   - bytes 1-2 hold the complete packed-file length, little-endian
;   - complemented packed-low offset bytes emitted by the current v5.2 encoder
;   - an explicit state-aware end marker; stream termination is in-band
;   - complemented gamma data bits for newly introduced distances
;   - the last negative offset lives on the stack instead of in RAM
;   - measured-safe RAM-tail auto-copy through CBS_V52_RAM_TAIL
;
; Not supported by v0.5:
;   - validation of reserved bits, destination bits or unsupported formats
;   - image/row formats, VRAM, ROM and mapper variants
;
; The code is intentionally long-labelled and commented. It is a reference
; source first, ready for code-size and cycle optimization.
;
; The entry trusts compressor output. Invalid data can read or write outside the
; intended buffers. This is the baseline for a register optimization pass.
;
; Syntax uses ordinary Z80 mnemonics accepted by most modern assemblers.
; Local-label syntax may need a small adjustment for an older assembler.
;
; License: zlib
; =============================================================================

; -----------------------------------------------------------------------------
; CBS_V52_RAM_TAIL
;
; Input:
;   HL = complete 01h/81h packed file at its current address
;   DE = start of the destination block
;   BC = complete expanded length / destination-block length
;
; The compressor must report the selected stream as tail-safe. The routine
; copies the complete packed file backward to DE+BC-packedLength and enters the
; normal forward decruncher. Both register banks are temporary during this
; one-time relocation; the hot decoder below remains unchanged.
; -----------------------------------------------------------------------------
CBS_V52_RAM_TAIL:
	PUSH	HL			; duplicate entry parameters in shadow registers
	PUSH	DE
	PUSH	BC
	EXX
	POP	BC			; BC = expanded length
	POP	DE			; DE = destination start
	POP	HL			; HL = packed-file start

	INC	HL
	LD	C,(HL)			; packed length low
	INC	HL
	LD	B,(HL)			; packed length high
	DEC	HL
	DEC	HL			; HL = packed-file start
	ADD	HL,BC
	DEC	HL			; HL = last packed byte
	PUSH	DE			; preserve destination start

	EXX				; original BC still holds expanded length
	EX	DE,HL			; HL = destination start
	ADD	HL,BC
	DEC	HL			; HL = destination-block last byte
	EX	DE,HL			; DE = destination-block last byte
	PUSH	DE

	EXX
	POP	DE			; DE = destination-block last byte
	LDDR				; relocate complete header + payload backward
	INC	DE			; DE = relocated packed-file start
	EX	DE,HL			; HL = relocated packed-file start
	POP	DE			; DE = destination start

	PUSH	HL			; return the active register bank to the caller's
	PUSH	DE			; normal set before entering the shared decoder
	EXX
	POP	DE
	POP	HL
	JP	CBS_V52_RAM_TO_RAM

; -----------------------------------------------------------------------------
; CBS_V52_RAM_TO_RAM
;
; Input:
;   HL = combined FF000DDD header
;   DE = destination in RAM
;
; Registers:
;   HL = packed source, DE = output destination, BC = gamma/copy work.
;   A = sentinel control-bit reservoir. AF, AF', BC, DE and HL are changed.
;   The shadow BC/DE/HL set is untouched.
;
; Stack:
;   The routine keeps the current negative offset above the caller's return
;   address. Nested CALLs remain safe; the offset is removed before final RET.
; -----------------------------------------------------------------------------
CBS_V52_RAM_TO_RAM:
	LD	A,(HL)
	INC	HL
	RLCA			; header bit 7 -> Carry
	SBC	A,A			; 00h=format 0, FFh=format 2
	EX	AF,AF'			; A'=format mode

	INC	HL			; skip packed length low
	INC	HL			; skip packed length high

	LD	A,80h			; sentinel control-bit reservoir
	LD	BC,FFFFh			; default negative offset = -1
	PUSH	BC			; persistent last offset on stack

; The first token is always a literal run.
	CALL	CBS_V52_GET_GAMMA
	LDIR

; After a literal:
;   0 + gamma(1) + 0 = one-byte match using the previous offset
;   0 + gamma(1) + 1 = end marker
;   0 + gamma(2...) = match using the previous offset
;   1 = match introducing a new offset
CBS_V52_AFTER_LITERAL:
	ADD	A,A
	JR	NC,CBS_V52_REPEAT_OFFSET
	CALL	Z,CBS_V52_REFILL_BITS
	JR	C,CBS_V52_NEW_OFFSET

CBS_V52_REPEAT_OFFSET:
	CALL	CBS_V52_GET_GAMMA			; BC = match length
	JR	C,CBS_V52_COPY_MATCH

; gamma(1): one extra bit distinguishes a one-byte match from end.
	ADD	A,A
	JR	NC,CBS_V52_COPY_MATCH
	CALL	Z,CBS_V52_REFILL_BITS
	JR	C,CBS_V52_FINISHED

; -----------------------------------------------------------------------------
; Copy BC bytes from DE+negativeOffset to DE.
;
; No CALL is used: the stack top is the persistent negative offset. EX (SP),HL
; temporarily swaps it with the packed pointer, PUSH/POP preserves the offset
; across LDIR, and the second exchange restores both values.
; -----------------------------------------------------------------------------
CBS_V52_COPY_MATCH:
	EX	(SP),HL			; HL=negative offset, stack=packed HL
	PUSH	HL			; preserve negative offset
	ADD	HL,DE			; HL=match source
	LDIR			; overlapping expansion is intentional
	POP	HL			; HL=negative offset
	EX	(SP),HL			; HL=packed source, stack=last offset

; After a match:
;   0 + gamma(2...) = literal run
;   0 + gamma(1) + 0 = one literal byte
;   0 + gamma(1) + 1 = end marker
;   1 = match introducing a new offset
CBS_V52_AFTER_MATCH:
	ADD	A,A
	JR	NC,CBS_V52_LITERAL_AFTER_MATCH
	CALL	Z,CBS_V52_REFILL_BITS
	JR	C,CBS_V52_NEW_OFFSET

CBS_V52_LITERAL_AFTER_MATCH:
	CALL	CBS_V52_GET_GAMMA
	JR	C,CBS_V52_COPY_LITERALS

; gamma(1): one extra bit distinguishes a one-byte literal from end.
	ADD	A,A
	JR	NC,CBS_V52_COPY_LITERALS
	CALL	Z,CBS_V52_REFILL_BITS
	JR	C,CBS_V52_FINISHED

CBS_V52_COPY_LITERALS:
	LDIR
	JR	CBS_V52_AFTER_LITERAL

; -----------------------------------------------------------------------------
; Decode a new match offset and its length.
; -----------------------------------------------------------------------------
CBS_V52_NEW_OFFSET:
; A' holds the format mode while A owns the control-bit reservoir.
	EX	AF,AF'
	OR	A
	JR	Z,CBS_V52_GENERAL_RESTORE_AF
	EX	AF,AF'

; Format-2 selector: 0 = gamma distance 1-15, 1 = format-0 form.
	ADD	A,A
	JR	NC,CBS_V52_NEW_OFFSET_NEAR
	CALL	Z,CBS_V52_REFILL_BITS
	JR	C,CBS_V52_NEW_OFFSET_GENERAL

CBS_V52_NEW_OFFSET_NEAR:
	CALL	CBS_V52_GET_NEG_GAMMA			; BC = -distance-1
	INC	BC			; BC = negative near distance
	EX	(SP),HL			; HL=old offset, stack=packed pointer
	POP	HL			; discard old offset, restore packed HL
	PUSH	BC			; install new persistent offset
	CALL	CBS_V52_GET_GAMMA			; BC = match length - 1
	INC	BC
	JR	CBS_V52_COPY_MATCH

CBS_V52_GENERAL_RESTORE_AF:
	EX	AF,AF'

CBS_V52_NEW_OFFSET_GENERAL:
	CALL	CBS_V52_GET_NEG_GAMMA			; BC = -high-1
	INC	BC			; BC = negative high offset part

; The stored offset is the negative of:
;   distance = (high - 1) * 128 + (127 - (packedLow >> 1)) + 1
;
; The compressor already complements the seven packed low bits. The first
; RR B extracts bit 8 of -high, then LD B,C keeps its low byte. RR B / RR C
; shifts that nine-bit value and packed byte together. The packed byte's bit 0
; exits in Carry as the first gamma(length-1) control bit.
	RR	B			; Carry=bit 8 of -high
	LD	B,C			; B=low eight bits of -high
	LD	C,(HL)			; complemented low7 << 1 | length control
	INC	HL			; preserves Carry
	RR	B			; insert high bit, expose low bit
	RR	C			; complete -distance, expose length control
	EX	(SP),HL			; HL=old offset, stack=packed pointer
	POP	HL			; discard old offset, restore packed HL
	PUSH	BC			; install new persistent offset

; Carry already contains packedLow bit 0.
	CALL	CBS_V52_GET_GAMMA_FIRST
	INC	BC			; BC = match length
	JR	CBS_V52_COPY_MATCH

CBS_V52_FINISHED:
	POP	BC			; discard persistent last offset
	RET

; -----------------------------------------------------------------------------
; Interleaved bit refill.
;
; Stream control bytes and literal/data bytes share HL. A starts at 80h. Its
; sentinel 1 eventually shifts into Carry exactly when a new control byte is
; required. RLA injects that sentinel into bit 0 and returns the new byte's
; original bit 7 in Carry. No RAM counter or control-byte workspace is needed.
;
; Every hot bit site performs ADD A,A directly. CALL Z reaches this routine
; only once per control byte; ordinary bits pay no unconditional CALL/RET.
; A must remain reserved for the reservoir between bit sites.
; -----------------------------------------------------------------------------
CBS_V52_REFILL_BITS:
	LD	A,(HL)
	INC	HL
	RLA			; sentinel -> bit 0, source bit 7 -> Carry
	RET

; -----------------------------------------------------------------------------
; Interlaced Elias gamma decoder.
;
; CBS_V52_GET_GAMMA reads its first control bit from the stream.
; CBS_V52_GET_GAMMA_FIRST accepts that control bit in Carry, used by format 0 where
; packedLow bit zero backtracks into gamma(length-1).
;
; Newly introduced distances store complemented gamma data bits. They enter the
; same loop through CBS_V52_GET_NEG_GAMMA with BC=FFFEh, producing -value-1.
; The caller increments BC once. This combines both former gamma loops without
; a mode branch inside the loop.
;
; Positive gamma returns Carry clear only for value 1 and set for values 2+.
; The token grammar uses that distinction for its compact end markers.
; -----------------------------------------------------------------------------
CBS_V52_GET_GAMMA:
	LD	BC,0001h
	JR	CBS_V52_GAMMA_READ_CONTROL

CBS_V52_GET_NEG_GAMMA:
	LD	BC,FFFEh

CBS_V52_GAMMA_READ_CONTROL:
	ADD	A,A
	JR	NC,CBS_V52_GAMMA_LOOP
	CALL	Z,CBS_V52_REFILL_BITS
	JR	NC,CBS_V52_GAMMA_LOOP
	CCF			; gamma(1) returns Carry clear
	RET

CBS_V52_GET_GAMMA_FIRST:
	LD	BC,0001h
	JR	NC,CBS_V52_GAMMA_LOOP
	CCF			; gamma(1) returns Carry clear
	RET

CBS_V52_GAMMA_LOOP:
	ADD	A,A			; data bit
	CALL	Z,CBS_V52_REFILL_BITS
	RL	C
	RL	B
	ADD	A,A			; next control bit
	JR	NC,CBS_V52_GAMMA_LOOP
	CALL	Z,CBS_V52_REFILL_BITS
	JR	NC,CBS_V52_GAMMA_LOOP
	RET
