solitaire-c64/mouse.c65

307 lines
8.7 KiB
Text

#IFNDEF __lib_mouse
#DEFINE __lib_mouse 1
GOTO __skip_lib_mouse
// ============================================================================
// COMMODORE 1351 MOUSE DRIVER FOR C64
// ============================================================================
// This library provides mouse input reading for the Commodore 1351 mouse.
//
// The 1351 uses the SID chip's POT lines to report relative movement.
// The SID hardware automatically samples at ~2kHz, but software should
// read once per frame (50-60Hz) to minimize CPU usage.
//
// Mouse is read from CONTROL PORT 1 (POT lines + buttons)
// This allows simultaneous use with joystick on CONTROL PORT 2
//
// Based on implementations from C64 OS and cc65.
//
// FAST MOVEMENT SMOOTHING:
// By default, directional momentum smoothing is enabled to fix the 1351's
// known issue where fast movements can register in the opposite direction.
// To disable smoothing (saves CPU cycles), define MOUSE_NO_SMOOTHING:
// #DEFINE MOUSE_NO_SMOOTHING 1
// #INCLUDE "mouse.c65"
//
// Usage:
// #INCLUDE "mouse.c65"
// mouse_init()
// WHILE 1
// mouse_read() // Call once per frame
// pointer_update_mouse(mouse_delta_x, mouse_delta_y)
// WEND
// ============================================================================
// SID POT line addresses
WORD CONST SID_BASE = $D400
BYTE pot_x @ SID_BASE+$19 // SID+$19: POT X (analog input)
BYTE pot_y @ SID_BASE+$1A // SID+$1A: POT Y (analog input)
// CIA port for button reading (same as joystick port 1)
WORD CONST CIA1_BASE = $DC00
BYTE cia_port1 @ CIA1_BASE+1 // $DC01: Port 1 (mouse buttons)
// Mouse state variables
BYTE mouse_pot_x_old // Previous POT X reading
BYTE mouse_pot_y_old // Previous POT Y reading
BYTE mouse_delta_x // Signed X movement delta
BYTE mouse_delta_y // Signed Y movement delta
BYTE mouse_buttons // Button state (bit 4=right, bit 0=left)
#IFNDEF MOUSE_NO_SMOOTHING
// Directional momentum tracking (for fast movement disambiguation)
// 0=no momentum, 1-127=rightward, 128-255=leftward (unsigned byte as signed)
// Define MOUSE_NO_SMOOTHING before including this library to disable smoothing
BYTE mouse_momentum_x
BYTE mouse_momentum_y
// ============================================================================
// FUNC mouse_apply_momentum
// Apply directional momentum to disambiguate fast movements
// When delta magnitude is large (ambiguous), use momentum to determine
// if we should flip the interpretation (±64 in 6-bit space)
// ============================================================================
FUNC mouse_apply_momentum({BYTE delta} {BYTE momentum} out:{BYTE corrected_delta} out:{BYTE new_momentum})
BYTE abs_delta
BYTE is_negative
BYTE is_ambiguous
BYTE was_corrected
// Get absolute value and sign of delta
is_negative = delta & $80
IF is_negative
abs_delta = 0 - delta
ELSE
abs_delta = delta
ENDIF
// Check if delta is ambiguous (magnitude >= 12 after division by 2 in convert function)
// In raw 6-bit space, deltas near ±32 become ±16 after /2, which is ambiguous
is_ambiguous = 0
was_corrected = 0
#PRAGMA _P_USE_LONG_JUMP 1
IF abs_delta >= 12
#PRAGMA _P_USE_LONG_JUMP 0
is_ambiguous = 1
// Ambiguous - check if flipping (negating) would match momentum better
BYTE flipped_delta
BYTE flipped_negative
// To flip interpretation, just negate the delta
flipped_delta = abs_delta
IF is_negative
// Delta is negative, flipped would be positive
flipped_negative = 0
ELSE
// Delta is positive, flipped would be negative
flipped_negative = 1
ENDIF
// Use momentum to decide: if momentum sign matches flipped, use flipped
IF momentum != 0
BYTE momentum_is_left
BYTE signs_match
momentum_is_left = 0
IF momentum >= 128
momentum_is_left = 1
ENDIF
signs_match = 0
IF momentum_is_left == flipped_negative
signs_match = 1
ENDIF
IF signs_match == 1
// Momentum suggests flipped interpretation
IF flipped_negative
corrected_delta = 0 - flipped_delta
ELSE
corrected_delta = flipped_delta
ENDIF
was_corrected = 1
ELSE
// Keep original
corrected_delta = delta
was_corrected = 1
ENDIF
ELSE
// No momentum yet - ignore this ambiguous delta, return 0
corrected_delta = 0
ENDIF
ELSE
// Unambiguous, use as-is
corrected_delta = delta
ENDIF
// Update momentum based on corrected delta
// Only update from unambiguous deltas to avoid bootstrapping in wrong direction
#PRAGMA _P_USE_LONG_JUMP 1
IF is_ambiguous == 0
#PRAGMA _P_USE_LONG_JUMP 0
IF corrected_delta == 0
// No movement - decay momentum toward zero
IF momentum > 0
IF momentum < 128
IF momentum > 2
momentum = momentum - 2
ELSE
momentum = 0
ENDIF
ELSE
IF momentum < 253
momentum = momentum + 2
ELSE
momentum = 0
ENDIF
ENDIF
ENDIF
ELSE
// Add corrected delta to momentum (clamped)
BYTE corrected_negative
corrected_negative = corrected_delta & $80
IF corrected_negative
// Moving left - push momentum toward 128-255
IF momentum < 128
momentum = 128
ELSE
IF momentum < 240
momentum = momentum + 8
ENDIF
ENDIF
ELSE
// Moving right - push momentum toward 1-127
IF momentum >= 128
momentum = 127
ELSE
IF momentum < 120
momentum = momentum + 8
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
new_momentum = momentum
FEND
#IFEND
// ============================================================================
// FUNC mouse_convert_pot_delta
// Convert 6-bit POT delta to 8-bit signed delta
// The 1351 outputs 6-bit signed values in bits 1-6 (bits 0,7 are noise)
// This converts them to standard 8-bit signed values (-128 to +127)
// ============================================================================
FUNC mouse_convert_pot_delta({BYTE new_val} {BYTE old_val} out:{BYTE delta})
BYTE diff
BYTE sign_bit
// Calculate raw difference (wraps at 64 due to 6-bit counter)
diff = new_val - old_val
// Mask to 7 bits (ignore bit 7 noise)
diff = diff & %01111111
// Check if value is negative (bit 6 set means >= 64 in 6-bit space)
sign_bit = diff & %01000000
IF sign_bit
// Negative value: set high bits and divide by 2
diff = diff | %11000000 // Sign extend
ASM
lda |diff|
cmp #$FF
beq @zero
sec
ror // Arithmetic shift right (preserves sign)
sta |diff|
jmp @done
@zero:
lda #0
sta |diff|
@done:
ENDASM
delta = diff
ELSE
// Positive value: just divide by 2
ASM
lda |diff|
beq @zero2
lsr // Logical shift right
sta |diff|
jmp @done2
@zero2:
lda #0
sta |diff|
@done2:
ENDASM
delta = diff
ENDIF
FEND
// ============================================================================
// FUNC mouse_init
// Initialize mouse driver - call once at program start
// ============================================================================
FUNC mouse_init
// Read initial pot values
mouse_pot_x_old = pot_x
mouse_pot_y_old = pot_y
mouse_delta_x = 0
mouse_delta_y = 0
mouse_buttons = 0
#IFNDEF MOUSE_NO_SMOOTHING
mouse_momentum_x = 0
mouse_momentum_y = 0
#IFEND
FEND
// ============================================================================
// FUNC mouse_read
// Read mouse movement and button state
// Call this every frame to update mouse state
// Updates mouse_delta_x, mouse_delta_y (signed 8-bit deltas)
// Applies directional momentum to fix fast movement bugs
// ============================================================================
FUNC mouse_read
BYTE pot_x_new
BYTE pot_y_new
// Read current POT values
pot_x_new = pot_x
pot_y_new = pot_y
// Calculate X delta
mouse_convert_pot_delta(pot_x_new, mouse_pot_x_old, mouse_delta_x)
mouse_pot_x_old = pot_x_new
#IFNDEF MOUSE_NO_SMOOTHING
// Apply momentum correction to X
mouse_apply_momentum(mouse_delta_x, mouse_momentum_x, mouse_delta_x, mouse_momentum_x)
#IFEND
// Calculate Y delta (invert for correct screen movement)
mouse_convert_pot_delta(pot_y_new, mouse_pot_y_old, mouse_delta_y)
mouse_delta_y = 0 - mouse_delta_y // Negate Y
mouse_pot_y_old = pot_y_new
#IFNDEF MOUSE_NO_SMOOTHING
// Apply momentum correction to Y
mouse_apply_momentum(mouse_delta_y, mouse_momentum_y, mouse_delta_y, mouse_momentum_y)
#IFEND
// Read buttons from CIA port
mouse_buttons = cia_port1
FEND
LABEL __skip_lib_mouse
#IFEND