From cd7607e330bbbcd6ba0bfca4a0ef189b4b18606b Mon Sep 17 00:00:00 2001 From: Daniel Covington Date: Wed, 27 May 2026 07:26:19 -0400 Subject: [PATCH 1/2] vim testing --- .claude/settings.local.json | 13 +- samples/kernal-os-skeleton/build_all.bat | 40 + samples/kernal-os-skeleton/build_vim.bat | 15 + samples/kernal-os-skeleton/kernal_os.asm | 145 +++- samples/kernal-os-skeleton/kernal_os.d64 | Bin 0 -> 174848 bytes samples/kernal-os-skeleton/kernal_os.o | Bin 35654 -> 37565 bytes samples/kernal-os-skeleton/kernal_os.prg | Bin 4317 -> 4577 bytes samples/kernal-os-skeleton/run.bat | 16 + samples/kernal-os-skeleton/vim.asm | 907 +++++++++++++++++++++++ samples/kernal-os-skeleton/vim.cfg | 11 + samples/kernal-os-skeleton/vim.o | Bin 0 -> 12932 bytes samples/kernal-os-skeleton/vim.prg | Bin 0 -> 1369 bytes 12 files changed, 1140 insertions(+), 7 deletions(-) create mode 100644 samples/kernal-os-skeleton/build_all.bat create mode 100644 samples/kernal-os-skeleton/build_vim.bat create mode 100644 samples/kernal-os-skeleton/kernal_os.d64 create mode 100644 samples/kernal-os-skeleton/run.bat create mode 100644 samples/kernal-os-skeleton/vim.asm create mode 100644 samples/kernal-os-skeleton/vim.cfg create mode 100644 samples/kernal-os-skeleton/vim.o create mode 100644 samples/kernal-os-skeleton/vim.prg diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 46b187e..4984c7c 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,18 @@ "permissions": { "allow": [ "Bash(build.bat)", - "PowerShell(Copy-Item \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\cc65-mouse-driver-demo\\\\mousedemo.c\" \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\cc65-mouse-driver-demo\\\\mouse_driver_demo.c\" -Force)" + "PowerShell(Copy-Item \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\cc65-mouse-driver-demo\\\\mousedemo.c\" \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\cc65-mouse-driver-demo\\\\mouse_driver_demo.c\" -Force)", + "PowerShell($f = \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\kernal-os-skeleton\\\\kernal_os.prg\"; \\(Get-Item $f\\).Length)", + "PowerShell($output = & \"C:\\\\Program Files\\\\GTK3VICE-3.10-win64\\\\bin\\\\x64sc.exe\" -help 2>&1; $output | Where-Object { $_ -match \"device|fs|drive|disk\" } | Select-Object -First 40)", + "PowerShell(cmd /c \"cd /d `\"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\kernal-os-skeleton`\" && build_all.bat\" 2>&1)", + "Bash(cmd.exe /c build_all.bat)", + "Bash(cmd.exe /c \"build_all.bat\")", + "Bash(echo \"exit: $?\")", + "Bash(cmd.exe /c \"ca65 kernal_os.asm -o kernal_os.o && ld65 -C kernal_os.cfg kernal_os.o -o kernal_os.prg && echo kernal_os OK\")", + "Bash(ca65 \"c:/Development/Commodore/os Experiments/c64os/samples/kernal-os-skeleton/kernal_os.asm\" -o \"c:/Development/Commodore/os Experiments/c64os/samples/kernal-os-skeleton/kernal_os.o\")", + "Bash(cmd /c build_all.bat)", + "Bash(cmd /c \"build_all.bat && echo BUILD_OK || echo BUILD_FAILED\")", + "Bash(echo \"EXIT: $?\")" ] } } diff --git a/samples/kernal-os-skeleton/build_all.bat b/samples/kernal-os-skeleton/build_all.bat new file mode 100644 index 0000000..7580c71 --- /dev/null +++ b/samples/kernal-os-skeleton/build_all.bat @@ -0,0 +1,40 @@ +@echo off +setlocal + +set "C1541=C:\Program Files\GTK3VICE-3.10-win64\bin\c1541.exe" +set "DISK=kernal_os.d64" + +rem ---- build kernal_os.prg ---- +call build.bat +if errorlevel 1 goto error + +rem ---- build vim.prg ---- +call build_vim.bat +if errorlevel 1 goto error + +rem ---- verify c1541 ---- +if not exist "%C1541%" ( + echo c1541.exe not found at: + echo %C1541% + exit /b 1 +) + +rem ---- create fresh D64 and write both PRGs ---- +"%C1541%" -format "kernal os,01" d64 "%DISK%" +if errorlevel 1 goto error + +"%C1541%" "%DISK%" -write "kernal_os.prg" "kernal_os" +if errorlevel 1 goto error + +"%C1541%" "%DISK%" -write "vim.prg" "vim" +if errorlevel 1 goto error + +echo. +echo built %DISK% (kernal_os + vim) +goto end + +:error +echo build failed. +exit /b 1 + +:end diff --git a/samples/kernal-os-skeleton/build_vim.bat b/samples/kernal-os-skeleton/build_vim.bat new file mode 100644 index 0000000..4adf7a2 --- /dev/null +++ b/samples/kernal-os-skeleton/build_vim.bat @@ -0,0 +1,15 @@ +@echo off + +ca65 vim.asm -o vim.o +if errorlevel 1 goto error + +ld65 -C vim.cfg vim.o -o vim.prg +if errorlevel 1 goto error + +echo built vim.prg successfully. +goto end + +:error +echo build failed. + +:end diff --git a/samples/kernal-os-skeleton/kernal_os.asm b/samples/kernal-os-skeleton/kernal_os.asm index 956da72..712b0ea 100644 --- a/samples/kernal-os-skeleton/kernal_os.asm +++ b/samples/kernal-os-skeleton/kernal_os.asm @@ -34,6 +34,7 @@ CHKOUT = $FFC9 CLRCH = $FFCC BASIN = $FFCF CLALL = $FFE7 +LOAD = $FFD5 ; ------------------------------------------------------------ ; system locations @@ -55,6 +56,7 @@ INPUT_MAX = 64 DISK_DEV = 8 LFN_DIR = 2 LFN_CMD = 15 +EXEC_AREA = $2000 SYS_ENTRY = 2061 ; ------------------------------------------------------------ @@ -1171,12 +1173,137 @@ check_exit: jmp request_exit command_unknown: - lda #msg_unknown + jmp do_exec + +command_empty: + rts + +; ------------------------------------------------------------ +; external program loader +; ------------------------------------------------------------ +; When the typed word does not match any built-in command, +; search the current disk device for a PRG file with that name. +; If found, load it to EXEC_AREA ($2000) and JSR there. +; The external program should end with RTS to return to the shell. +; All external programs must be assembled for address $2000. +; ------------------------------------------------------------ + +do_exec: + jsr build_exec_filename + bcc exec_try_load + jmp exec_not_found + +exec_try_load: + jsr reset_file_state + + lda #LFN_DIR + sta active_lfn + ldx current_device + ldy #1 + jsr SETLFS + + lda filename_len + ldx #filename_spec + jsr SETNAM + + lda #0 + ldx #EXEC_AREA + jsr LOAD + bcc exec_loaded + + jsr reset_file_state + jmp exec_not_found + +exec_loaded: + jsr reset_file_state + jsr set_os_memory + + lda #msg_exec_run jsr print_string + + ldy #0 +exec_print_name: + lda input_buffer,y + beq exec_print_done + cmp #' ' + beq exec_print_done + jsr CHROUT + iny + bne exec_print_name + +exec_print_done: + lda #CR + jsr CHROUT + + lda #EXEC_AREA + sta PTR1_HI + jsr call_indirect + jsr set_os_memory rts -command_empty: +exec_not_found: + lda #msg_exec_nf + jsr print_string + + ldy #0 +exec_nf_name: + lda input_buffer,y + beq exec_nf_done + cmp #' ' + beq exec_nf_done + jsr CHROUT + iny + bne exec_nf_name + +exec_nf_done: + lda #CR + jsr CHROUT + rts + +; JMP (PTR1) — turns a JSR into an indirect call. +; Caller does: JSR call_indirect +; The called code's RTS returns to call_indirect's caller. +call_indirect: + jmp (PTR1_LO) + +; build filename_spec = "{drive}:{cmdname}" from input_buffer word 0 +build_exec_filename: + ldx #0 + lda current_drive + clc + adc #'0' + sta filename_spec,x + inx + lda #':' + sta filename_spec,x + inx + + ldy #0 + +exec_copy_name: + lda input_buffer,y + beq exec_name_done + cmp #' ' + beq exec_name_done + sta filename_spec,x + inx + iny + cpx #35 + bcc exec_copy_name + sec + rts + +exec_name_done: + stx filename_len + lda #0 + sta filename_spec,x + clc rts ; ------------------------------------------------------------ @@ -1958,9 +2085,6 @@ msg_banner: msg_prompt: .byte "OS> ", 0 -msg_unknown: - .byte "UNKNOWN. HELP", CR, 0 - msg_help: .byte CR .byte "HELP MEM INC DUMP ZERO SIGN", CR @@ -1969,6 +2093,7 @@ msg_help: .byte "HEX RUN RENAME COPY", CR .byte "DEVICE DEV DRIVE DEL ERASE", CR .byte "ABOUT EXIT", CR + .byte "OTHER: LOAD+RUN PRG AT $2000", CR .byte CR .byte 0 @@ -2003,6 +2128,8 @@ msg_about: .byte "KERNAL STAYS MAPPED IN.", CR .byte "SHELL CODE STARTS AT $080D.", CR .byte "USE $A000-$BFFF AS WORK RAM.", CR + .byte "EXTERNAL CMDS LOAD AT $2000.", CR + .byte "ASSEMBLE CMDS WITH *=$2000.", CR .byte CR .byte 0 @@ -2178,6 +2305,12 @@ msg_copy: msg_copy_usage: .byte "USAGE: COPY srcname dstname", CR, 0 +msg_exec_run: + .byte "EXEC: ", 0 + +msg_exec_nf: + .byte "? ", 0 + .segment "BSS" workspace_counter: diff --git a/samples/kernal-os-skeleton/kernal_os.d64 b/samples/kernal-os-skeleton/kernal_os.d64 new file mode 100644 index 0000000000000000000000000000000000000000..1a1425aeff1e07c36630ed9234796eeaa69df7f6 GIT binary patch literal 174848 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 literal 0 HcmV?d00001 diff --git a/samples/kernal-os-skeleton/kernal_os.o b/samples/kernal-os-skeleton/kernal_os.o index 0c664a20f34c78d3e78281c0a960c7bb277a1aa7..11d0623a97714ee9f4f6778989fd731c594564f4 100644 GIT binary patch literal 37565 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 diff --git a/samples/kernal-os-skeleton/kernal_os.prg b/samples/kernal-os-skeleton/kernal_os.prg index 5b3d2d443a9cf3843b3df7e9660d898290422bae..2c09c37df7147328c603e1dc3f773328207b4b7b 100644 GIT binary patch literal 4577 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 literal 4317 zcmbVPeQZtl`!)7V_VxH=q9!C#Hhul?hn2Ug%B#1TENg$(7bc* zdoOWGVcnwW-E+^^Js-bwZ&K%L^aa>2)`UAFj4^nn3j3?@!z#R|z*rEr<8R1V;e(AK zcs-Oqr&bmjM-*86ut%ayoXobtuFxBGFxVRjx zFIV_Sbw6xx6`N7#XGA!svdkqF);437qCRt8J@PcqGI*<+_wk%R%i&lRPE;or8GluB z_3$oH*A+bevYPo&h4-tOvix3D^JP`Js8*85!{7WRwXz!5%9_j`Ui@-J&3~v?KF(iO zON&Jv8t3Vy5&<=)LMtMP|17zh3EcCheu_;;|-B_7Ex z#rDB{;>llCb2C|WfddhdX13$#d(G%*^8!a+7wSK=3r-|r*Sw=JoMNjcLzwea>4wZVALW%sXbR=KIo5aa!?t*R-nbu(=0Z1_eg;?W-m#8QurH;UyReYH`5M~^j$$7|>(Jv!5bca!TA0%Bbay%A{{!ylSNuSb8< zBnCX4kb)R?ZEZz-&7+ShVzu)U*>BXX`j@}q)jEe*f&{jTW$_im{^0|JF91C$SJcc^ zHRI!%2+yqHnYA1NpOJS$CdxDGd1hJ_KAahR@@#x3k1w07%T^Z6l>SLOyx!Cz(DoM5 zO+_dXec+0!+EM%P74=O|W2$tEXsLnpYtvE#A6RR0MxDv8mi={N6{ zj*zq;r8(xt;O7(re&=F9daC@GjKRlM`-qR1A1}0$$^mzef%#Dq#gd35#k?S&X<3ON z*2>MPZDKZRi)U(*tr0#-!uf>JYy9(hZJzhKAOxow_df;m#WF@XwP{5$Jn>kuyYOiA zS{PA*S788=5eTR|)^c?R!X(0{N*_?hXo4{5=2@)0k_8WmKj{|$&t^AY9C%thogO+Jw>j*NN_^1IF9|3-PDMU z6F0Ej-JDLQ?-SWJV^`?j8$FMJ#TRKc-o8#Dkg(;SYIuLNqQR?oUb&lPzqwOsLdo=< z(#`3+#_kX~9p`Ydir3Lm1uj=BH;YQ;iDFx2>5*|>-dMN{$6N96SyaNmrG6U9kMm0D z)HoMy&K|X7bk2oGhoHyZ{K#&O^MUWHJhJ64dw8i053QfRJG+}3(;-J*si%rQ$Vi4{l-9L&Bx08f+v&{vyaCX^o2OhJ1%8jb*rn8`tCkA=fw1r3Lx z&rCvmsMk^yW?CD7r4xe*b0pCLBvqKBG^7oIo`^$ldT1ENw4@1^-k(sccv906(5olG zO2txX3zhT~#0^W);sYk6%yfJJQlrBf7-p;&tk@tz^L68So)ZTK`U+>%s#+0`?VCZs3m&L(8u&d|2!A% zVyXB5@%nT_LzK+P(pIb=oly&(-?n}03(xM@3J4O}jyVn{jLnaw$bmLAoEp8K8wUfP zBZax%B|dg;-L+G?Ca}oO28j`Q%ABgi&jJS$Pz$((u$mZ232V%VjSCP=EQP_!6e2Jl zOCd95LQlG{PeTY}4Oc15-N~dnfXXkA>eG`}szYI3UFWJKksEy~vW_w#1QWebPr^DZ zbtH+9>D8@4Fo$u1v}6*E+Z~Rc7$(HKEYl)@GdPhV#m>S_pocS4Q(nHNyL0 zb2pAeI*}j@w8tWNqcBBkJpfhi)PH~b7Ky7Dw!TCwp$Yvyz>dW5;t!saR$E@&=~(>- DfJi0D diff --git a/samples/kernal-os-skeleton/run.bat b/samples/kernal-os-skeleton/run.bat new file mode 100644 index 0000000..e771423 --- /dev/null +++ b/samples/kernal-os-skeleton/run.bat @@ -0,0 +1,16 @@ +@echo off +setlocal + +set "VICE_EXE=C:\Program Files\GTK3VICE-3.10-win64\bin\x64sc.exe" +set "DISK=kernal_os.d64" + +if not exist "%VICE_EXE%" ( + echo VICE x64sc was not found at: + echo %VICE_EXE% + exit /b 1 +) + +call build_all.bat +if errorlevel 1 exit /b 1 + +"%VICE_EXE%" -autostart "%CD%\%DISK%" diff --git a/samples/kernal-os-skeleton/vim.asm b/samples/kernal-os-skeleton/vim.asm new file mode 100644 index 0000000..c74a9bc --- /dev/null +++ b/samples/kernal-os-skeleton/vim.asm @@ -0,0 +1,907 @@ +; +; vim.asm - minimal modal text editor for the kernal-os-skeleton shell +; +; Build: +; ca65 vim.asm -o vim.o +; ld65 -C vim.cfg vim.o -o vim.prg +; +; From the OS shell type: VIM +; All external commands load at $2000 and end with RTS. +; +; Modes +; NORMAL (default) h j k l or cursor keys = move +; i = insert before cursor +; a = append after cursor +; o = open line below +; x = delete char under cursor +; : = enter command mode +; INSERT type text, DEL = backspace, RUN/STOP = back to NORMAL +; COMMAND :q quit :w NAME save to disk +; RUN/STOP = cancel +; +; Text model: the 24x40 text area of screen RAM IS the buffer. +; Row 24 (bottom line) is the status bar. +; + +.setcpu "6502" + +; ---- KERNAL ---- +GETIN = $FFE4 +STOPKEY = $FFE1 +CHROUT = $FFD2 +SETLFS = $FFBA +SETNAM = $FFBD +OPEN = $FFC0 +CLOSE = $FFC3 +CHKOUT = $FFC9 +CLRCH = $FFCC +CLALL = $FFE7 + +; ---- hardware ---- +SCRRAM = $0400 + +; ---- layout ---- +SCR_COLS = 40 +SCR_ROWS = 24 ; rows 0-23 = text, row 24 = status bar + +; ---- modes ---- +MODE_NORMAL = 0 +MODE_INSERT = 1 +MODE_CMD = 2 + +; ---- PETSCII ---- +CR_CH = $0D +DEL_CH = $14 + +; ---- command buffer ---- +CMD_MAX = 20 + +; ---- save LFN ---- +SAVE_LFN = 5 +SAVE_DEV = 8 + +; ---- zero page (user area, safe under OS banking) ---- +ZP_LO = $FB ; general destination pointer lo +ZP_HI = $FC ; general destination pointer hi +STR_LO = $FD ; string source pointer lo +STR_HI = $FE ; string source pointer hi +ZP_TMP = $02 ; scratch byte + +; ================================================================ +; PRG load address header +; ================================================================ +.segment "LOADADDR" + .word $2000 + +; ================================================================ +; CODE +; ================================================================ +.segment "CODE" + +; ---- entry ---- +vim_main: + jsr CLALL ; close any open channels from shell + jsr vim_init + jsr full_redraw + +main_loop: + lda quit_flag + beq ml_no_quit + jmp vim_do_exit +ml_no_quit: + + jsr get_key ; waits for keypress; $FF = RUN/STOP + cmp #$FF + bne ml_key_ok + ; RUN/STOP pressed + lda cur_mode + beq ml_stop_quit ; normal mode: quit + lda #MODE_NORMAL ; other modes: back to normal + sta cur_mode + jsr full_redraw + jmp main_loop +ml_stop_quit: + lda #1 + sta quit_flag + jmp main_loop +ml_key_ok: + sta last_key + lda cur_mode + cmp #MODE_INSERT + bne ml_not_insert + jmp handle_insert +ml_not_insert: + cmp #MODE_CMD + bne ml_not_cmd + jmp handle_cmd +ml_not_cmd: + ; fall through = normal mode + +; ================================================================ +; NORMAL MODE +; ================================================================ +handle_normal: + lda last_key + cmp #'H' + beq nm_left + cmp #$9D ; cursor-left hardware key + beq nm_left + cmp #'L' + beq nm_right + cmp #$1D ; cursor-right hardware key + beq nm_right + cmp #'K' + beq nm_up + cmp #$91 ; cursor-up hardware key + beq nm_up + cmp #'J' + beq nm_down + cmp #$11 ; cursor-down hardware key + beq nm_down + cmp #'I' + beq nm_ins + cmp #'A' + beq nm_app + cmp #'X' + beq nm_del + cmp #'O' + beq nm_open + cmp #':' + beq nm_cmd + jmp nm_done +nm_left: + jsr cursor_left + jmp nm_done +nm_right: + jsr cursor_right + jmp nm_done +nm_up: + jsr cursor_up + jmp nm_done +nm_down: + jsr cursor_down + jmp nm_done +nm_ins: + lda #MODE_INSERT + sta cur_mode + jmp nm_done +nm_app: + jsr cursor_right + lda #MODE_INSERT + sta cur_mode + jmp nm_done +nm_del: + jsr delete_char + jmp nm_done +nm_open: + jsr open_line_below + lda #MODE_INSERT + sta cur_mode + jmp nm_done +nm_cmd: + lda #MODE_CMD + sta cur_mode + lda #0 + sta cmd_len +nm_done: + jsr full_redraw + jmp main_loop + +; ================================================================ +; INSERT MODE +; ================================================================ +handle_insert: + lda last_key + cmp #CR_CH + beq ins_nl + cmp #DEL_CH + beq ins_bs + cmp #$20 ; ignore control chars below $20 + bcc ins_done + cmp #$7F + bcs ins_done + jsr insert_char ; A = PETSCII char to insert + jsr cursor_right + jmp ins_done +ins_nl: + jsr do_newline + jmp ins_done +ins_bs: + jsr backspace_char +ins_done: + jsr full_redraw + jmp main_loop + +; ================================================================ +; COMMAND MODE +; ================================================================ +handle_cmd: + lda last_key + cmp #CR_CH + beq cmd_exec + cmp #DEL_CH + beq cmd_bs + cmp #$20 + bcc cmd_done + cmp #$7F + bcs cmd_done + ldx cmd_len + cpx #CMD_MAX + bcs cmd_done + sta cmd_buf,x + inc cmd_len + jmp cmd_done +cmd_bs: + lda cmd_len + beq cmd_done + dec cmd_len + jmp cmd_done +cmd_exec: + jsr run_cmd +cmd_done: + jsr full_redraw + jmp main_loop + +; ================================================================ +; EXIT +; ================================================================ +vim_do_exit: + jsr restore_cursor ; de-invert cursor char before leaving + rts ; return to kernal-os-skeleton shell + +; ================================================================ +; COMMAND EXECUTION +; ================================================================ +run_cmd: + lda cmd_len + beq rc_cancel + + ; --- :Q quit --- + cmp #1 + bne rc_not_q + lda cmd_buf + cmp #'Q' + bne rc_not_q + lda #1 + sta quit_flag + lda #MODE_NORMAL + sta cur_mode + rts + +rc_not_q: + ; --- :W [NAME] save --- + lda cmd_buf + cmp #'W' + bne rc_unknown + jsr do_save + lda #MODE_NORMAL + sta cur_mode + rts + +rc_unknown: + ; show "?" in status then return to normal + lda #msg_cmd_err + jsr show_status_text +rc_cancel: + lda #MODE_NORMAL + sta cur_mode + rts + +; ================================================================ +; FILE SAVE +; do_save: reads cmd_buf "W NAME" and writes text to disk +; ================================================================ +do_save: + ; need at least "W N" (3 chars: W, space, one char of name) + lda cmd_len + cmp #3 + bcs ds_len_ok + jmp ds_no_name +ds_len_ok: + lda cmd_buf + 1 + cmp #' ' + beq ds_space_ok + jmp ds_no_name +ds_space_ok: + + ; copy filename from cmd_buf[2..] to save_fn + ldy #2 + ldx #0 +ds_copy_name: + lda cmd_buf,y + beq ds_name_done + cmp #' ' + beq ds_name_done + sta save_fn,x + inx + iny + cpx #15 + bcc ds_copy_name +ds_name_done: + stx save_fn_len + lda #0 + sta save_fn,x + + ; build "0:NAME,S,W" in full_fn + jsr build_save_fname + + ; SETLFS + lda #SAVE_LFN + ldx #SAVE_DEV + ldy #2 + jsr SETLFS + + ; SETNAM + lda full_fn_len + ldx #full_fn + jsr SETNAM + + jsr OPEN + bcc ds_open_ok + ; open error + lda #msg_disk_err + jsr show_status_text + rts + +ds_open_ok: + lda #SAVE_LFN + jsr CHKOUT + bcc ds_write_rows + jsr CLRCH + lda #SAVE_LFN + jsr CLOSE + lda #msg_disk_err + jsr show_status_text + rts + +ds_write_rows: + lda #0 + sta save_row + +ds_row_loop: + ldx save_row + lda row_lo,x + sta ZP_LO + lda row_hi,x + sta ZP_HI + + ; find rightmost non-space column + ldy #SCR_COLS - 1 +ds_find_end: + lda (ZP_LO),y + cmp #$20 + bne ds_row_found + tya + beq ds_row_empty + dey + jmp ds_find_end + +ds_row_empty: + ; empty row: just write CR + lda #CR_CH + jsr CHROUT + jmp ds_row_done + +ds_row_found: + ; write cols 0..Y then CR + sty save_col_end + ldy #0 +ds_write_col: + lda (ZP_LO),y + jsr screen_to_petscii + jsr CHROUT + cpy save_col_end + beq ds_write_cr + iny + jmp ds_write_col + +ds_write_cr: + lda #CR_CH + jsr CHROUT + +ds_row_done: + inc save_row + lda save_row + cmp #SCR_ROWS + bne ds_row_loop + + ; close + jsr CLRCH + lda #SAVE_LFN + jsr CLOSE + + lda #msg_saved + jsr show_status_text + rts + +ds_no_name: + lda #msg_need_name + jsr show_status_text + rts + +; builds "0:save_fn,S,W" into full_fn, length into full_fn_len +build_save_fname: + ldx #0 + lda #'0' + sta full_fn,x + inx + lda #':' + sta full_fn,x + inx + ldy #0 +bsf_loop: + lda save_fn,y + beq bsf_suffix + sta full_fn,x + inx + iny + bne bsf_loop +bsf_suffix: + lda #',' + sta full_fn,x + inx + lda #'S' + sta full_fn,x + inx + lda #',' + sta full_fn,x + inx + lda #'W' + sta full_fn,x + inx + stx full_fn_len + lda #0 + sta full_fn,x + rts + +; ================================================================ +; CURSOR MOVEMENT +; ================================================================ +cursor_left: + lda cur_col + beq cl_wrap + dec cur_col + rts +cl_wrap: + lda cur_row + beq cl_top + dec cur_row + lda #SCR_COLS - 1 + sta cur_col +cl_top: rts + +cursor_right: + lda cur_col + cmp #SCR_COLS - 1 + bcs cr_wrap + inc cur_col + rts +cr_wrap: + lda cur_row + cmp #SCR_ROWS - 1 + bcs cr_bot + inc cur_row + lda #0 + sta cur_col +cr_bot: rts + +cursor_up: + lda cur_row + beq cu_top + dec cur_row +cu_top: rts + +cursor_down: + lda cur_row + cmp #SCR_ROWS - 1 + bcs cd_bot + inc cur_row +cd_bot: rts + +; ================================================================ +; TEXT EDITING +; ================================================================ + +; Set ZP_LO/ZP_HI to the screen RAM base for cur_row +row_ptr: + ldx cur_row + lda row_lo,x + sta ZP_LO + lda row_hi,x + sta ZP_HI + rts + +; Insert PETSCII char (in A) at cursor; shift rest of line right (last char lost) +insert_char: + jsr petscii_to_screen + sta ZP_TMP + jsr row_ptr + ldy #SCR_COLS - 1 +ic_shift: + cpy cur_col + beq ic_write + dey + lda (ZP_LO),y + iny + sta (ZP_LO),y + dey + jmp ic_shift +ic_write: + lda ZP_TMP + ldy cur_col + sta (ZP_LO),y + rts + +; Delete char at cursor; shift rest of line left, fill end with space +delete_char: + jsr row_ptr + ldy cur_col +dc_shift: + cpy #SCR_COLS - 1 + beq dc_space + iny + lda (ZP_LO),y + dey + sta (ZP_LO),y + iny + jmp dc_shift +dc_space: + lda #$20 + sta (ZP_LO),y + rts + +; Backspace: move cursor left then delete +backspace_char: + lda cur_col + beq bs_done + jsr cursor_left + jsr delete_char +bs_done:rts + +; RETURN in insert mode: move to col 0 of next row +do_newline: + lda cur_row + cmp #SCR_ROWS - 1 + bcs nl_done + inc cur_row + lda #0 + sta cur_col +nl_done:rts + +; Open new line below: advance row, clear it, col = 0 +open_line_below: + lda cur_row + cmp #SCR_ROWS - 1 + bcs ol_done + inc cur_row + lda #0 + sta cur_col + jsr clear_current_row +ol_done:rts + +clear_current_row: + jsr row_ptr + ldy #0 + lda #$20 +ccr_lp: sta (ZP_LO),y + iny + cpy #SCR_COLS + bne ccr_lp + rts + +; Clear all 24 text rows to spaces +clear_text: + lda #0 + sta ZP_TMP +ct_loop: + ldx ZP_TMP + lda row_lo,x + sta ZP_LO + lda row_hi,x + sta ZP_HI + ldy #0 + lda #$20 +ct_row: sta (ZP_LO),y + iny + cpy #SCR_COLS + bne ct_row + inc ZP_TMP + lda ZP_TMP + cmp #SCR_ROWS + bne ct_loop + rts + +; ================================================================ +; SCREEN RENDERING +; ================================================================ + +; Restore the saved char at (prev_row, prev_col) +restore_cursor: + ldx prev_row + lda row_lo,x + clc + adc prev_col + sta ZP_LO + lda row_hi,x + adc #0 + sta ZP_HI + lda prev_char + ldy #0 + sta (ZP_LO),y + rts + +; Show cursor by inverting char at (cur_row, cur_col); saves old char +show_cursor: + ldx cur_row + lda row_lo,x + clc + adc cur_col + sta ZP_LO + lda row_hi,x + adc #0 + sta ZP_HI + ldy #0 + lda (ZP_LO),y + sta prev_char + ora #$80 ; set bit 7 = reversed character + sta (ZP_LO),y + lda cur_row + sta prev_row + lda cur_col + sta prev_col + rts + +; Draw status bar at row 24 +draw_status: + ; clear status row + lda row_lo + 24 + sta ZP_LO + lda row_hi + 24 + sta ZP_HI + ldy #0 + lda #$20 +dstat_clr: + sta (ZP_LO),y + iny + cpy #SCR_COLS + bne dstat_clr + + lda cur_mode + cmp #MODE_INSERT + beq dstat_ins + cmp #MODE_CMD + beq dstat_cmd + + ; NORMAL + lda #str_normal + jsr write_str_to_status + rts + +dstat_ins: + lda #str_insert + jsr write_str_to_status + rts + +dstat_cmd: + ; write ":" then cmd_buf + lda row_lo + 24 + sta ZP_LO + lda row_hi + 24 + sta ZP_HI + lda #$3A ; screen code for ':' + ldy #0 + sta (ZP_LO),y + iny + ldx #0 +dstat_cmd_lp: + cpx cmd_len + beq dstat_done + lda cmd_buf,x + jsr petscii_to_screen + sta (ZP_LO),y + iny + inx + cpy #SCR_COLS + bcc dstat_cmd_lp +dstat_done: + rts + +; Write null-terminated PETSCII string (A=lo, Y=hi) to status row +write_str_to_status: + sta STR_LO + sty STR_HI + lda row_lo + 24 + sta ZP_LO + lda row_hi + 24 + sta ZP_HI + ldy #0 +wss_lp: + lda (STR_LO),y + beq wss_done + jsr petscii_to_screen + sta (ZP_LO),y + iny + cpy #SCR_COLS + bcc wss_lp +wss_done: + rts + +; Copy a PETSCII string directly to status (no screen-code conversion) +; Used for messages that already have screen codes (msg_saved etc.) +; Actually we just reuse write_str_to_status since it converts +show_status_text: + jsr write_str_to_status + rts + +; Restore cursor, redraw cursor, redraw status bar +full_redraw: + jsr restore_cursor + jsr show_cursor + jsr draw_status + rts + +; ================================================================ +; INIT +; ================================================================ +vim_init: + lda #0 + sta cur_row + sta cur_col + sta prev_row + sta prev_col + sta cur_mode + sta cmd_len + sta quit_flag + sta save_row + sta save_col_end + lda #$20 ; cursor starts on a space + sta prev_char + jsr clear_text + rts + +; ================================================================ +; KEY INPUT +; ================================================================ +get_key: +gk_loop: + jsr STOPKEY + bne gk_getin + lda #$FF ; RUN/STOP = special exit code + rts +gk_getin: + jsr GETIN + beq gk_loop ; no key yet, keep polling + rts + +; ================================================================ +; CHARACTER CONVERSION +; ================================================================ + +; PETSCII → screen code +; $41-$5A (A-Z unshifted) → $01-$1A (subtract $40) +; $61-$7A (a-z lowercase) → $01-$1A (subtract $60) +; $20-$3F (space,nums,punct) → same +; others: pass through +petscii_to_screen: + cmp #$41 + bcc pts_pass + cmp #$5B + bcc pts_upper + cmp #$61 + bcc pts_pass + cmp #$7B + bcc pts_lower +pts_pass: + rts +pts_upper: + sec + sbc #$40 + rts +pts_lower: + sec + sbc #$60 + rts + +; screen code → PETSCII (inverse of above) +; $01-$1A → $41-$5A (add $40) +; $20-$3F → same +; $00 → '@' +; others: return space $20 +screen_to_petscii: + cmp #$01 + bcc stp_at + cmp #$1B + bcc stp_letter + cmp #$20 + bcc stp_space + cmp #$40 + bcc stp_pass +stp_space: + lda #$20 + rts +stp_at: + lda #$40 ; '@' + rts +stp_letter: + clc + adc #$40 + rts +stp_pass: + rts + +; ================================================================ +; RODATA +; ================================================================ +.segment "RODATA" + +str_normal: + .byte "NORMAL (hjkl=move i=ins x=del :=cmd)", 0 + +str_insert: + .byte "INSERT (type text, DEL=bksp, STOP=normal)", 0 + +msg_cmd_err: + .byte "? unknown command", 0 + +msg_saved: + .byte "saved.", 0 + +msg_disk_err: + .byte "disk error.", 0 + +msg_need_name: + .byte "usage: :w filename", 0 + +; Row-base lookup tables (25 entries: rows 0-23 = text, row 24 = status) +row_lo: + .byte <(SCRRAM + 0*40), <(SCRRAM + 1*40), <(SCRRAM + 2*40) + .byte <(SCRRAM + 3*40), <(SCRRAM + 4*40), <(SCRRAM + 5*40) + .byte <(SCRRAM + 6*40), <(SCRRAM + 7*40), <(SCRRAM + 8*40) + .byte <(SCRRAM + 9*40), <(SCRRAM + 10*40), <(SCRRAM + 11*40) + .byte <(SCRRAM + 12*40), <(SCRRAM + 13*40), <(SCRRAM + 14*40) + .byte <(SCRRAM + 15*40), <(SCRRAM + 16*40), <(SCRRAM + 17*40) + .byte <(SCRRAM + 18*40), <(SCRRAM + 19*40), <(SCRRAM + 20*40) + .byte <(SCRRAM + 21*40), <(SCRRAM + 22*40), <(SCRRAM + 23*40) + .byte <(SCRRAM + 24*40) + +row_hi: + .byte >(SCRRAM + 0*40), >(SCRRAM + 1*40), >(SCRRAM + 2*40) + .byte >(SCRRAM + 3*40), >(SCRRAM + 4*40), >(SCRRAM + 5*40) + .byte >(SCRRAM + 6*40), >(SCRRAM + 7*40), >(SCRRAM + 8*40) + .byte >(SCRRAM + 9*40), >(SCRRAM + 10*40), >(SCRRAM + 11*40) + .byte >(SCRRAM + 12*40), >(SCRRAM + 13*40), >(SCRRAM + 14*40) + .byte >(SCRRAM + 15*40), >(SCRRAM + 16*40), >(SCRRAM + 17*40) + .byte >(SCRRAM + 18*40), >(SCRRAM + 19*40), >(SCRRAM + 20*40) + .byte >(SCRRAM + 21*40), >(SCRRAM + 22*40), >(SCRRAM + 23*40) + .byte >(SCRRAM + 24*40) + +; ================================================================ +; BSS (zero-initialised by the OS / KERNAL CINT on cold start; +; vim_init zeros what it cares about explicitly) +; ================================================================ +.segment "BSS" + +cur_row: .res 1 +cur_col: .res 1 +prev_row: .res 1 +prev_col: .res 1 +prev_char: .res 1 ; screen code saved before cursor inversion +cur_mode: .res 1 +cmd_len: .res 1 +cmd_buf: .res CMD_MAX + 1 +last_key: .res 1 +quit_flag: .res 1 +save_row: .res 1 +save_col_end: .res 1 +save_fn: .res 16 ; extracted filename (no path) +save_fn_len: .res 1 +full_fn: .res 24 ; "0:filename,S,W" +full_fn_len: .res 1 diff --git a/samples/kernal-os-skeleton/vim.cfg b/samples/kernal-os-skeleton/vim.cfg new file mode 100644 index 0000000..93b7bfc --- /dev/null +++ b/samples/kernal-os-skeleton/vim.cfg @@ -0,0 +1,11 @@ +MEMORY { + LOADADDR: start = $1FFE, size = $0002, type = ro, file = %O; + MAIN: start = $2000, size = $7FFF, type = rw, file = %O; +} + +SEGMENTS { + LOADADDR: load = LOADADDR, type = ro; + CODE: load = MAIN, type = ro; + RODATA: load = MAIN, type = ro; + BSS: load = MAIN, type = bss, define = yes; +} diff --git a/samples/kernal-os-skeleton/vim.o b/samples/kernal-os-skeleton/vim.o new file mode 100644 index 0000000000000000000000000000000000000000..8719f2057b6f00ed5d055020a0e7ddc683de0cf6 GIT binary patch literal 12932 zcmZ8n2Y6K1vA%QFR+6!Cnt4g=+%%WCVaE`g7r&r@VsI8j5H>E!TZz>Q6r`2ByD~VH zMF?a-AbJ(ZbOEAcs_0;eW*KZu?;VUq@96#WAMv#0e4qY1XU@z&bLO0T_U>K1^*4X| zqn2f*;V&M4S@qWduAU!S)))8_$UEwnHE9GPe*NP!i8DH>E0 zZPhfT;we=$wWLC3$3S6+A4dX^Evc1Rz$l_yKGsny{2cOytp4v((OLc9we0FT*#p`d zSs!Id6JmvY6Gh4xrThl^TIma$L`A#YlBvk7Sng|eu6D~1 z*7-e4UXu5qcwC-WlUs0oUeT`pqdcRUNyy+uMZ0>4cvSPaCC>uv>Nn*oV${lu>hmHL zkIFih`XtoP2+*!>D>JE@fY{Fp(5_CB57cWS5_nP3u6{?VXfy+=<^r^8LE%B3Q0r_Sh?1;94x>BR1N2RKhLG%`!5n z<o~d=M?@)Ru2)U2=QWY5%r$H@qz^;mpy1^HX%uW~%|5=s z+$QQk{Y@~D1-laAd<6?%Zp%6h6w9)RT$Y&|X*-h~t|{5!SPSICnbKUt!5e8VQ@U;v z;Ei-0bQRLqc<~PDtDB{f48#m;WghVRigxw)WHr2N*vQ{XME2}~U^W#ghFMYr5F7T;>pcWH6Zm^`XAI!K!#W*W2^0(M6d zSjHGNNzndg8hn&2cItZwLirW8EH_vucz==$1Za zcfYLnNFV4cqyVd=n?olV#w09g;OxjMu-0PpsI#J5%FTk(xg17iw*1MmCt30vO|c+i z5eiVITA2=Pq^NXzX>*hwMwF8t5XH%MIJCat;}8 zApJ6#9%WY9=%$0!k)ofs>Z3DIWr zWMV27db*ZJqRr+px+BmzvO8iecXKVr(m%(ze?<2j^?uDb8_jF#J4X|?!rXXs;FTst z$YhcwOZCQTDt9pLQu*2}zupIs^%m`;`L>Y7a48ipr^r#S4sO7KT4o835u6955jSNi zc5$stL5^|6QrT(>M&GRM`Fhf_}lAFk{ zRA!jPWwaU4q{(M+sE~Vxh{xzX5Y>uXcM@Q`oHrSA>^#Dz$wpXI$f4+j0vo8-CE#)q zx64vd{ZbA4l#IbZvGg(dkfIL)rpXqD?HfVeS3e}5k{{{GpRK_Gcmjy%-sj6jfi@(WD?a6yK1aJbgdlCPM-WixSRa2c93DJ0J%COZktwQ?5vDB^Z0 zj8XnVEPt8=G~kRVRm{*NACpV4pC`jG8b!Pyw^)o@C`Ou01oIrZ$0Y87vr<~}gyGTC zk|)ndxK_$moqqkvvlYxZdG3&592gvVggf1*H~u3SwB;PtJIozWskihFY$Y62@91%G zA!3Rb5EiQfvIJt0oWy$IFxvuEl~llO$ZeU8PLWiA)h(ZBp{+2gWkI&=skThVEN4q; z#MUS^ew}odC*bTzdwMaK_G-N+V!if&X3bOL(^Re_p#)-;9K^yPm5opp$q5)Cl{M&8 z$t9p$Hkk>cV?o9uI#Oi<59JQ1K=CSMZ$zZMT)$LHVg=yHEG?`R?x75`ShOK#di>ro zge=9-S4c6}xmZ8yACoeKpC>CZ>>^%}30(CFde7mr9y2bMX%TyN8aUE$C@NDw?xcR0 z4w&YX36LlNJca1y!Fvm^jD=6JER3k?LRNCLITT;fXUfrVN|SxmKiN>>CwWTs){hlO z7MPN>(zLzkElvuLuu&%;X82Z07$S2U;RJ2dpG8p+`6D`LDA0d5F zqZ;`Pk&h75q^I5_T@cC+W0@@JqOmJXY$jC!wkG&|v?NFNn4+UL7Gx&dAJwUp3A(QJ z$xc8jS(Z#jmdJS_VpYgOCA7o1hb+jP#VAv_Z$O+TUjpaIQLf!l$d$4qB844Hp)dAZ zj(i)j-@es)-j;4^UlNf)iOFEQW_U;Xqlc;uL_XN=MaZXERvQ-Pvl`>^z2qBH&YyBS z^3W(o*kWnZP@6V7_IX4CpCi>`xob46%3WyEWEyXcGE_^eqGb#>(W#f2nyp67Dr73$ z?ibArPu2O{k>4}lO4JAY>o{Dn%HPvPE`tjDs|@TlG7=;=i4}6ntP8&@oPqHluNvHcrQV~Vt&qh3EJz|3upa_c$SmB857uKA=8Jk< zNBVeX*o9u1C2Md&LB0ZEm3)fMBuo0CBIjkdsRF;j?B?_@V>%CuVx_d@I>u?*Z8gJF z=6K_|e#&qjhhdeB2lHuJ1{G?36g(yJ-*_dckS4RROA7DGU8L0Ko{WS877|s13AUYC`NDz09PI@uwjY)$Ysm%j> ziA;v-5;0BAKz3wk#L^Ar(j5S|BQ7pUS3THV+P`V+daL3l>>TM#3w#h+(pe{)$5X2c zti^JcNAOwL70V`5Xnu-sqUNO0@B}$YO*598_e3-G`7`_6p?}u?8|-tf-q8L(+2{A_ ze`g<7xdBa@w6mg|BXx2J z4mENc7^P^J*U2Ds(xktB8#-PmbGZx?ftN)wO@4=aCrMBx6niB!4=LK!?@K9F3$Y6E z0<^2s?QTFRrUSY@kbE*Q?>aR{9>S)k4$ zz2HzHrBFShXjlJ8o>0xlAbnU-zZn0IY8GIU(Ta9;OY9I;4Tf8Sq86%<+c3ZCYDIMD-xgAG7vn|)t-TvA=`}Le$ zc0TkuPS)#Les+S6)ARKVUp70A)8ch+PR29Z0jYYnZJ(Olu@h)s0!!$lJf9QtsoXR4 zoYOkl{kqCsx(D-~b5c90oYLWSe{LWr9kQMM3=lj}|IW$xclYP^kh&<#<#)WfIXUTm z?MMa@U+1E%*kJmHzKjo6CN8(c0#7HFGMuIUCi3PBU zl}P-FDjMJ*4HVgrNtf3W?y^#6^^qNIHOML@#(1ta0f9qlC5H)(#|gyra` zhFHsO%^*QJEvsp>g`>~w;1Ze-BKBrxS2;MAhBbOZTT@sMymLYOw`OpYgM*3di96_o zH?e46GF~rIP9)`7y5dVe+G1KxVm1pnI+i$r_WGvJI#0@Cs{f+#deizF;tdXdMI6C+ z{ix4YL$*tFT*xdYlXVg`0~z@gv5@{txaIJw0_`YSj?!y7@iH}=Y5xuF2UCOB4`}-+ z_hyChTPPZyqS1b)aFzkT=7jl-HHH?sn&T}};YA`^5d)o|yqNMZ(mtt!`>9yT8Js5J zdSXyn@Ol8Ph+MmgS2%c#_!U`368my=5%sf)Wt>u`(rja1H8iUR=PP}5oC zU1QYiWLZe{b{20WHQzGfGt{hPj4`Avp?)-NHd8)FV!X~ldrhGi&0DF6Pk*$rWFJF3 z$r3Cj&g19^>c`P(9}y4eXnVXj;^JAiBs6X zfCH%6M$2W?>>~Y4G92L29jE>V^)`{xrR=(Q+O2o2bDTXSD0IUrEb4j@~j1)Kt@JE^!D)k8`wun&HG=%p{~$ILH*{ zQ$3SGx~RIl2AWB25sfF3WCU?N0}Uei8fuo)a}Vt=Q|?6@JXfQgq5c%{I9K^Fy{Z{| zAN5D*HH%);h>NHhM7fjZw4CNAi26#uI@xHXEp4PR%XK8@Yw{(sv)xr^)PPq$PBkPQ1un_8IkWs>yUt055t~Cf7+0 z-lX{*a^clJ+EkAE)oDAOE_3h*)xS`S70d`<fGRiT|tdeyaZWIL8a*%~I_YMqW;rT^y|9U^z7>>E4I(Jcc{UaQ&#Mpym*< zmX`W$#rm2i1F0TN`VUk+nil8DK7;CW)EuH_3-JhXGDmka#unP(*&A&qF+r2XswCT%xvYDz^(R>f{ zxXRf0iiB27^MxEe$k7p$Pmo{*M~@NrQ9q0Nn}&^eo57FL_m@g}fhGs&bby8J$2`I` zxl8kHlsD4mG!fs<&<0TU=qfE@mIp|7l}=|EZwO7^SL>DvQ`zssu}$>pN9#k>++kIB zQa;9@U$9L0u8USkJVE_V;#&G%rTs8!`V;Zv6xuAxmzYyOVjoUzmD&9C?av&>Q=Uot zYm_Gw_tLAFHdm+_z*t{VldcnAzzJ0_&44QQ(_$?x*3*9xP38~>FoWgfI!>E&lqYhu zi1uGFkI$)JN1HFHUr5|T{W;pqA+Di*zOg6Ib2|N19Q=fMjY;9((Xaxvo=v+^#FHE> zA>zF|+9m3b6PFQ>(d!e&xJ}E2ln1fWQ!7Y^qojOezZqh!`+PE@z5%1 z>DAJ8)c!gqe1=iyQ~s8?jQ+!EJ&agR_JL#{NEttcqkYcND&k7&7Z6vG;Y;EiYCfZU zikA86I*w%HsGiHPqbaW>j-k_7;=eTEgUoUl73E~XH&wJ*l<{gF?MvDWrDho|*V1Mw z^`9`{$J`Z;nh&JmL>m5F6_pIVf{6FDXr;!J)muZu&6M$LC)!C`eomX+9Gyvd66H$9 z!&`Q=8cuK=0}iCk2Ko-5zK}Sbno;y!ORp8Ydn!5ld!^BrQdVDTr_nl9OH;rpeN5WP zM7*IxYpw7#?QW3uTV_cLJ{1UpZ zplb;Y%Sc&7&1f$A4LXHsd5HRBs;;r}1g-aCyS=(g_k_9GFB)K(AL z|3~-Lmzp=Yr*=+G?i8tL^H!_oFTdKfA$q&tyb{rSrP)hO8)CiE?2Xi>FE{i%H*bbu z4SjTzo4)MQ+;mD5H2-+ja3YEKCCnkg$eMn=L4$;(X<*sf9-vxsW;}0F~$o zcVk<%>6r3Xr&uhF`wyvlX`j?CI;BH$`{Rk2bM*<%mlh6rOfT@3>Nv-cI(o z|IpqDt&`u5d9Q=Voub&LZER|%j_B}$9(0}Bb#P&ho}Bb7f6NQPtiSo#&GKi35;OC& zvpoS{Mj*XMypDUab93|JKgiDtc`~!pyC&phd;GZ`?I$WlMy|)#Gbm@xePcL}4tAn|DJ`RG= z*!q2m$TXN6Al*atGAZNhJNbF{O_VwjOumrMz*imo7n<_A+u2$;_6n386(2g3I z7Ycf_$X-7jh}S&TH=~hCexEOcg>ZEVW-_q^6@vMhnOQyKusA4LcAPgG(@D=`AF0Kn zkGmqy8=yKC*DkrCSZ}^+W4&1T+)$iOAukkov_1z-$0X*;@_K^ZvatUlFfCWceT@pb znc}*bT^HxiHYZ~92X8Y2SQ+^=e^M|xdv0g;a&Py-@x)I{MAyZ*oVc^OO_&#h9 zFr9d6^d3m$CNf(vQIC!K4TZggjpgxX2kIqk{`)%I*uhW$8w|gYWPjI`RTVlv?nVxq zy9G;09&A~pXgWHL*d9S_);yP9cz1N|&Xira;xx+gyYfOoPhNU37_UNpUY;*N3HPZl z5E~5TdD26OlZVY13Smq|7S5%TI!3p`X&l=zrFBxLq^RGgrbesadM~+SN{6I3lB3(d S<#xN=DM_u9TDQi7@BaZuHrdbs literal 0 HcmV?d00001 diff --git a/samples/kernal-os-skeleton/vim.prg b/samples/kernal-os-skeleton/vim.prg new file mode 100644 index 0000000000000000000000000000000000000000..a5ee53c8e36dded4fdf01e882f70e0eff091b684 GIT binary patch literal 1369 zcmY*ZUuauZ7{9$qyQW>&txOvb<;zWuX|bD~ii|*9iw-uuu5%qGUH^FE8rRq+DNWK= z1RY{952oC{&4lFjRP?(N!n;C|=#o!@u9 zf9IU50Q_phsSt!iR-Oot-V=aWh}$*4H9|xyQ_3R*Io$`Wp`0f;mvaOu-ca-C-vd{1 z`U|Wa;pfD&d`Udbb#aP6E>7@Q5HE_S_{)foiEe%#aX+rb#q)eXoaPa6M*j|APLp!M zk<5`_NQu!#h|BE59fuACA1p8xZ7{>py$od*ZQ>DrTy%0U9_8;>I^YLQ`u-i@SWwEw zpj(V<8b(t;nrdkJ#L6hyI$r5q8Y8=-M1L}1jS}J8bRhHw+--0zNagwoEWg7VBmA{m ztJ4>!PgJhNYCf0e@;we}mrThg}!tPOs&b-(uUEHRbUI=mM`ePfmgZ&g<{hutj@t3bHF z_T|2J+bM^#4mbF+!g`lVWSd2#G?iO(L>!W^TKkjrILx3UT^y5m!IZU?2({uEY%`7^ zMnv;wjIeN7YH`BD74NP~@Xku}E>em^p`iy2y|(#}2N&iW^seT?Oi*? zKc*r#R@b{~|4^nB)lb!jntj0iqTW=ohqvjHgKm7N=6ocOvEiRArT`E1|)rSv)UbWOZHITcN3XOjS_XeyHfn2(MprvM_+tLgD><@|-g zvzIQTVg8-zB;=Fx`5t)btR8)HGB@1=gO>*`Ml;!&bYiMoIRk~vWF|Y8fveecI*}Py za*5gG_(^3vm79d*%uIIXq*BNwt|cQ7nS-}dQ^`ysom7;ri}Pkx`1>Y|ueO?d)RW(ddjOcN~)AKgp6W`2YX_ literal 0 HcmV?d00001 From da50d8f30139296568e2deeae7c60f4e80c8d165 Mon Sep 17 00:00:00 2001 From: Daniel Covington Date: Thu, 28 May 2026 04:49:34 -0400 Subject: [PATCH 2/2] skirmish --- .claude/settings.local.json | 4 +- skirmish-game/IMPLEMENTATION.md | 118 ++++++ skirmish-game/Makefile | 30 ++ skirmish-game/README.md | 48 +++ skirmish-game/build.bat | 13 + skirmish-game/data.o | Bin 0 -> 4307 bytes skirmish-game/debug-vice.bat | 6 + skirmish-game/docs/C64-CHEATSHEET.md | 333 +++++++++++++++ skirmish-game/docs/GDD.md | 158 +++++++ skirmish-game/docs/TECHNICAL-PLAN.md | 300 +++++++++++++ skirmish-game/enemy-ai.o | Bin 0 -> 5677 bytes skirmish-game/game-logic.o | Bin 0 -> 6890 bytes skirmish-game/input.o | Bin 0 -> 2592 bytes skirmish-game/render.o | Bin 0 -> 10462 bytes skirmish-game/run-vice.bat | 6 + skirmish-game/skirmish.o | Bin 0 -> 3316 bytes skirmish-game/skirmish.prg | Bin 0 -> 2625 bytes skirmish-game/src/c64.cfg | 19 + skirmish-game/src/data.asm | 132 ++++++ skirmish-game/src/enemy-ai.asm | 303 +++++++++++++ skirmish-game/src/game-logic.asm | 391 +++++++++++++++++ skirmish-game/src/input.asm | 112 +++++ skirmish-game/src/render.asm | 613 +++++++++++++++++++++++++++ skirmish-game/src/skirmish.asm | 136 ++++++ skirmish-game/src/skirmish.inc | 40 ++ 25 files changed, 2761 insertions(+), 1 deletion(-) create mode 100644 skirmish-game/IMPLEMENTATION.md create mode 100644 skirmish-game/Makefile create mode 100644 skirmish-game/README.md create mode 100644 skirmish-game/build.bat create mode 100644 skirmish-game/data.o create mode 100644 skirmish-game/debug-vice.bat create mode 100644 skirmish-game/docs/C64-CHEATSHEET.md create mode 100644 skirmish-game/docs/GDD.md create mode 100644 skirmish-game/docs/TECHNICAL-PLAN.md create mode 100644 skirmish-game/enemy-ai.o create mode 100644 skirmish-game/game-logic.o create mode 100644 skirmish-game/input.o create mode 100644 skirmish-game/render.o create mode 100644 skirmish-game/run-vice.bat create mode 100644 skirmish-game/skirmish.o create mode 100644 skirmish-game/skirmish.prg create mode 100644 skirmish-game/src/c64.cfg create mode 100644 skirmish-game/src/data.asm create mode 100644 skirmish-game/src/enemy-ai.asm create mode 100644 skirmish-game/src/game-logic.asm create mode 100644 skirmish-game/src/input.asm create mode 100644 skirmish-game/src/render.asm create mode 100644 skirmish-game/src/skirmish.asm create mode 100644 skirmish-game/src/skirmish.inc diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 4984c7c..c973f6e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -13,7 +13,9 @@ "Bash(ca65 \"c:/Development/Commodore/os Experiments/c64os/samples/kernal-os-skeleton/kernal_os.asm\" -o \"c:/Development/Commodore/os Experiments/c64os/samples/kernal-os-skeleton/kernal_os.o\")", "Bash(cmd /c build_all.bat)", "Bash(cmd /c \"build_all.bat && echo BUILD_OK || echo BUILD_FAILED\")", - "Bash(echo \"EXIT: $?\")" + "Bash(echo \"EXIT: $?\")", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\skirmish-game\\\\\" -Recurse -File | Select-Object FullName | ForEach-Object { $_.FullName })", + "PowerShell(Test-Path \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\skirmish-game\\\\skirmish.prg\" && \\(Get-Item \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\skirmish-game\\\\skirmish.prg\"\\).Length)" ] } } diff --git a/skirmish-game/IMPLEMENTATION.md b/skirmish-game/IMPLEMENTATION.md new file mode 100644 index 0000000..41606a0 --- /dev/null +++ b/skirmish-game/IMPLEMENTATION.md @@ -0,0 +1,118 @@ +# Implementation Progress — Skirmish (C64) + +## Status: BUILDABLE MVP BASELINE + +Last updated: 2026-05-28 + +--- + +## What Exists Now + +### Project structure + +``` +skirmish-game/ +├── src/ +│ ├── skirmish.asm # Entry point, raster sync, shared state +│ ├── input.asm # Keyboard + joystick polling +│ ├── game-logic.asm # Turn flow, movement, combat, occupancy +│ ├── enemy-ai.asm # Closest-target AI and movement +│ ├── render.asm # Sprites, HUD, board markers +│ ├── data.asm # Tables, text, sprite bitmaps +│ ├── skirmish.inc # Shared constants +│ └── c64.cfg # ld65 memory layout +├── docs/ +│ ├── GDD.md +│ ├── TECHNICAL-PLAN.md +│ └── C64-CHEATSHEET.md +├── build.bat +├── run-vice.bat +├── debug-vice.bat +├── Makefile +├── README.md +└── skirmish.prg +``` + +### Core gameplay implemented + +- 8x8 tactical board with coordinate markers +- 3 player units and 3 enemy units +- Single-step turn flow: + - player selects a unit + - player moves or attacks + - enemy team takes its turn + - win/lose is checked +- Adjacent melee combat for both teams +- Unit HP tracking and death removal +- Greedy enemy AI using Manhattan distance +- Selected-unit highlight via sprite color +- Restart flow after win or loss + +### Controls implemented + +- `Q` or joystick fire: cycle selected player unit +- `W`, `A`, `S`, `D` or joystick directions: move or attack +- `E` or space: end turn +- `R`: restart after game over + +### Engine and rendering implemented + +- BASIC stub starts game with `RUN` -> `SYS 2061` +- Raster-driven frame sync installed through IRQ vector +- Main loop updates once per IRQ tick +- 6 hardware sprites used for units +- HUD shows current phase, turn count, selected unit, and all unit HP +- Sprite bitmaps and lookup tables are linked into C64-visible RAM +- Occupancy grid rebuilt from unit state to avoid stale collision state + +--- + +## Build Verification + +### Confirmed + +- `build.bat` assembles and links successfully with local `ca65` and `ld65` +- Output artifact currently builds as `skirmish.prg` + +### Current artifact + +- `skirmish.prg` size: 2617 bytes +- Most recent successful local build: 2026-05-28 + +### Not yet verified + +- Full gameplay smoke test inside VICE +- Sprite alignment and board readability in the live emulator +- Joystick port 2 behavior in VICE input mapping +- End-to-end win/lose flow by playtesting + +--- + +## Known Gaps + +### High priority + +1. Run the build in `x64sc` and verify the boot path, controls, and turn flow. +2. Confirm the raster-driven loop behaves correctly on a real VICE session. +3. Tune board readability if the current dot-grid is too sparse under sprites. + +### Medium priority + +1. Add attack and death feedback such as flash or simple SID beeps. +2. Add a stronger title/opening screen. +3. Add a clearer wait indicator and maybe a per-unit action prompt. + +### Low priority + +1. Add obstacles and richer pathing. +2. Add ranged attacks or special abilities. +3. Add difficulty scaling or multiple waves. + +--- + +## Notes + +- The previous tracker text described files that did not actually exist yet. +- The source tree and build scripts now exist and match this document. +- The PRG boot path was corrected so the BASIC stub jumps to the real contiguous load address. +- The next milestone should be emulator validation and gameplay polish, not project scaffolding. diff --git a/skirmish-game/Makefile b/skirmish-game/Makefile new file mode 100644 index 0000000..4f355a7 --- /dev/null +++ b/skirmish-game/Makefile @@ -0,0 +1,30 @@ +CA65 ?= ca65 +LD65 ?= ld65 + +OBJS = skirmish.o input.o game-logic.o enemy-ai.o render.o data.o + +all: skirmish.prg + +skirmish.prg: $(OBJS) + $(LD65) $(OBJS) -C src/c64.cfg -o $@ + +skirmish.o: src/skirmish.asm src/skirmish.inc + $(CA65) src/skirmish.asm -o $@ + +input.o: src/input.asm src/skirmish.inc + $(CA65) src/input.asm -o $@ + +game-logic.o: src/game-logic.asm src/skirmish.inc + $(CA65) src/game-logic.asm -o $@ + +enemy-ai.o: src/enemy-ai.asm src/skirmish.inc + $(CA65) src/enemy-ai.asm -o $@ + +render.o: src/render.asm src/skirmish.inc + $(CA65) src/render.asm -o $@ + +data.o: src/data.asm src/skirmish.inc + $(CA65) src/data.asm -o $@ + +clean: + rm -f $(OBJS) skirmish.prg diff --git a/skirmish-game/README.md b/skirmish-game/README.md new file mode 100644 index 0000000..84ab389 --- /dev/null +++ b/skirmish-game/README.md @@ -0,0 +1,48 @@ +# Skirmish + +`Skirmish` is a single-screen C64 tactical prototype built in 6510 assembly with `ca65`/`ld65`. + +## Controls + +- `Q` or joystick fire: cycle the selected player unit +- `W`, `A`, `S`, `D` or joystick directions: move or attack +- `E` or space: end your turn without moving +- `R`: restart after a win or loss + +## Build + +### Windows + +```bat +build.bat +``` + +### Cross-platform + +```bash +make +``` + +## Run + +### Normal + +```bat +run-vice.bat +``` + +If you load it manually in BASIC, use `RUN`. The built PRG contains a BASIC stub that executes `SYS 2061`. + +### With monitor + +```bat +debug-vice.bat +``` + +## Current MVP + +- 3 player units and 3 enemy units on an 8x8 board +- Keyboard and joystick input +- Single-step movement or adjacent melee attacks +- Greedy enemy AI using Manhattan distance +- Win/lose flow with restart diff --git a/skirmish-game/build.bat b/skirmish-game/build.bat new file mode 100644 index 0000000..108f196 --- /dev/null +++ b/skirmish-game/build.bat @@ -0,0 +1,13 @@ +@echo off +setlocal +cd /d "%~dp0" + +ca65 src\skirmish.asm -o skirmish.o || exit /b 1 +ca65 src\input.asm -o input.o || exit /b 1 +ca65 src\game-logic.asm -o game-logic.o || exit /b 1 +ca65 src\enemy-ai.asm -o enemy-ai.o || exit /b 1 +ca65 src\render.asm -o render.o || exit /b 1 +ca65 src\data.asm -o data.o || exit /b 1 +ld65 skirmish.o input.o game-logic.o enemy-ai.o render.o data.o -C src\c64.cfg -o skirmish.prg || exit /b 1 + +echo Built skirmish.prg diff --git a/skirmish-game/data.o b/skirmish-game/data.o new file mode 100644 index 0000000000000000000000000000000000000000..b60b0afe1757414f55a1f86edf60bfd8a2a92d06 GIT binary patch literal 4307 zcmaKuU2Ie58ONW;#PN3=@(xU!6AgAr)zx)SFG+&92e3Nl09*7n8zB07w8;;A7xZ{)TjZr0i4MAboxm_K z58UP%tX!}LIDsHA&AnK;;1l2h@E8d2Ijmf;2;2eg0e|LwUxw>w5IlSqVPqTl46247 z%H8^mpU=PW6!G)W%IAqafD32=e4AEVov;Kn^S4EKm8|Si+LbNJfU-^5z9nx`O-eg| zSH@{lTH2?&OBqnMDV<7}(yI4-A#rFz$}T=b><8L`0MG%rfF^(d2e--B4e$YWAOQG* z9h+GT6am|L93_HQzz%pfvkoW*w6$Tg!3X6Bb^zKPc!2;wKr_3c1a`gxbpn)rK?JW*PjIg3EB0&L@Lu!4JlgSW#9 z)&M`BfE7FfcJVB%;4v`7-_pUsb=54CMc)^UcQX^3xa@`Q?P;{(9H&l z1yx{(UnbrQtOJL+A6D>t;AQ#U5G(iqV2%>s2UY-itHBCX$*{yDfGW9DP%mu7Ha?6V zf_s32PZIwNSOffAz`6yGfNtJ}RSK%WAiuE{Rlb2`3A%wtyo?hG{J5R!}1{GE&NS+ciWPyQnSEw zB28Z9oB!0tA21)0v0I(STi2?W^G5Wm8wB^XDL&|r}R z5{w}@qQOVZrzA0ubZe5~hyOqkR3qr3&2-^II;nhECL4XyHMD3QakV>>ORmlRM0X9Tm)7$+=KSR@cW@cBB&4c zO+cP_*@Rk3>~W|CIY{=dL_M(Y0C@o0M!CxJk@q&yPN=V;Ap#}0i|9OT70`S6meoyc zKa^Vz8iU%6;vE2-ELJ{8ATvM*>=~$GAO^e&bfbq8H~`xPH49Y&_QA@gnCPppy-@PX zuy*9MgzQCRStt`Y0V_YXL|0(10{D8<>rerxH=r&9*EIw70P>o{;~*ag)P7|8ke`DZ zu}VbY=Zr>S+fjEK>V4p*-k+1AXb^q|mk=hzeq{9QfM}zgVPaf&p;`;gz1?%VP z>mN@Bd+UB`pkGc__hOA;uM=~_l2l;+E+o1T&xVpUxGjz8*9z)NCatBl`Kqo>RY%1v zRbYZTkTo}z*0gHFV@BR`DSP((Y-P4*wtT5GJ3G)HIMv&8=vd%jU_4(5WV45}eZg$A zRJ~rlm@i$*m*;!(3t4wM7D;8o@pLTZO+-U8hG`j(5i=%dEbTTX6DiR~H0ntip~;L@ z=vJ+D-efA2GHSWUGTKnn;Rz#r!e!-Ck(k?3(Rk8uMq-I{Dw9sQEioFOiq&M)7)yDq z)QpTzq-yb4#58I$W=wCSQ=v%8S1UGTcr)crhE2nWWz5i|D;$r;O(;(?VMZ|1l$msm z#6#w&EHPSBW_-$LWr@hN5zS0LZXr`DE=h;2Za(kL77N8vt!ayy&$}!6N+Fl2w!t7kGV4cYtxE}awCDwAh wR~q>TthfMYBZYh(NHS1Z6uj&fN-8R%y=R+ZZyQw(Wt|^)=8rO0U)NYWdHyG literal 0 HcmV?d00001 diff --git a/skirmish-game/debug-vice.bat b/skirmish-game/debug-vice.bat new file mode 100644 index 0000000..3650a15 --- /dev/null +++ b/skirmish-game/debug-vice.bat @@ -0,0 +1,6 @@ +@echo off +setlocal +cd /d "%~dp0" + +call build.bat || exit /b 1 +"C:\Program Files\GTK3VICE-3.10-win64\bin\x64sc.exe" -autostart skirmish.prg -autostart-warp -nativemonitor -keepmonopen -refreshonbreak diff --git a/skirmish-game/docs/C64-CHEATSHEET.md b/skirmish-game/docs/C64-CHEATSHEET.md new file mode 100644 index 0000000..01ff47a --- /dev/null +++ b/skirmish-game/docs/C64-CHEATSHEET.md @@ -0,0 +1,333 @@ +# C64 6502 Assembly Cheat Sheet + +--- + +## 1. Memory Map + +``` +$0000–$00FF Zero page (fast access, 2-byte instructions) +$0100–$01FF Stack (grows downward from $01FF) +$0200–$1FFF System/free area +$2000–$3FFF User RAM (40 KB available) +$D000–$DFFF I/O ports (VIC-II, SID, CIA1, CIA2) +$E000–$FFFF Kernal ROM (can be banked out for more RAM) +``` + +--- + +## 2. Key VIC-II Registers + +All addresses relative to $D000 (e.g., $D010 = $D000 + $10). + +### Sprite Coordinates + +``` +$D000–$D00F Sprite X positions (16 bytes, 2 per sprite) + $D000 = Sprite 0 X (low byte) + $D010 = X high bits (bit 0 = Sprite 0 X bit 8, etc.) + +$D001–$D00F Sprite Y positions (16 bytes, 2 per sprite) + $D001 = Sprite 0 Y + $D003 = Sprite 1 Y + ... (every other byte) +``` + +### Sprite Control + +``` +$D015 Sprite enable ($00 = all off, $3F = all 6 on) +$D017 Sprite Y expand (1 = double height) +$D01D Sprite X expand (1 = double width) +$D025 Sprite 0 color +$D026 Sprite 1 color +... (one byte per sprite) + +$D027 Sprite 0 multicolor (0–3 for multicolor mode) +$D028 Sprite 1 multicolor +... +$D01C Multicolor mode enable (bit = sprite index) +``` + +### Screen Control + +``` +$D011 VIC control register 1 + Bit 7: Raster interrupt request + Bit 6: Extended color text mode + Bit 5: Bitmap mode + Bit 4: Blank screen (1 = blank) + Bits 3–0: Scroll Y + +$D012 Raster line (read for current, write for interrupt trigger) + +$D016 VIC control register 2 (scroll X, multicolor, etc.) + +$D018 Memory control (screen addr, bitmap addr) + Default: $14 (screen at $0400, bitmap at $2000) +``` + +### Colors + +``` +$D020 Border color +$D021 Background color 0 +$D022 Background color 1 (multicolor mode) +$D023 Background color 2 (multicolor mode) +$D024 Background color 3 (multicolor mode) +``` + +--- + +## 3. CIA (Complex Interface Adapter) — Input + +### CIA1 ($DC00–$DC0F) + +``` +$DC00 Data direction register A (keyboard columns) +$DC01 Keyboard matrix input register + Bit 0 = Key Row + Bit 1 = Key Row, etc. + +$DC02 Data direction register B +$DC03 Joystick / paddle input + +$DC04–$DC05 Timer A (low/high) +$DC06–$DC07 Timer B (low/high) +$DC0E Control register A (timer, interrupt) +$DC0F Interrupt enable register +``` + +### Simple Joystick Read + +``` +LDA #$00 ; Set port B for input +STA $DC02 +LDA $DC01 ; Read joystick (port 2) or keyboard +; Bit pattern: 7=up, 6=down, 5=left, 4=right, 3=fire +``` + +--- + +## 4. Common 6502 Instructions + +### Load/Store + +```asm +LDA #$42 ; Load immediate +LDA $42 ; Load from zero page +LDA $4200 ; Load from absolute address +LDA $4200,X ; Indexed +LDA ($42,X) ; Indirect indexed +STA $42 ; Store +``` + +### Arithmetic + +```asm +ADC #$10 ; Add with carry +SBC #$10 ; Subtract with carry +INC $42 ; Increment memory +DEC $42 ; Decrement memory +INX, INY, DEX, DEY ; Increment/decrement registers +``` + +### Branching + +```asm +BNE $4000 ; Branch if not equal (after CMP, etc.) +BEQ $4000 ; Branch if equal +BCS $4000 ; Branch if carry set +BCC $4000 ; Branch if carry clear +BPL $4000 ; Branch if plus (MSB = 0) +BMI $4000 ; Branch if minus (MSB = 1) +JMP $4000 ; Unconditional jump +JSR $4000 ; Jump to subroutine (push return address) +RTS ; Return from subroutine +``` + +### Comparisons + +```asm +CMP #$10 ; Compare A with immediate (sets flags) +CMP $42 ; Compare A with memory +CPX #$10 ; Compare X +CPY #$10 ; Compare Y +; Flags: Z=zero (equal), C=carry (unsigned <), N=minus +``` + +### Bit Operations + +```asm +ASL ; Arithmetic shift left (A or M) +LSR ; Logical shift right +ROL ; Rotate left +ROR ; Rotate right +AND #$0F ; Bitwise AND +ORA #$F0 ; Bitwise OR +EOR #$FF ; Bitwise XOR +BIT $42 ; Test bits (sets N, V, Z) +``` + +### Stack + +```asm +PHA ; Push A +PLA ; Pull A +PHP ; Push status +PLP ; Pull status +``` + +--- + +## 5. Interrupt Handling + +### Setup IRQ Handler + +```asm +; In initialization: +LDA #IRQ_HANDLER ; High byte +STA $0315 + +CLI ; Clear interrupt disable flag +``` + +### IRQ Handler Structure + +```asm +IRQ_HANDLER: + PHA ; Save registers + TXA + PHA + TYA + PHA + + ; Check if it's our interrupt (raster or timer) + LDA $D019 ; VIC interrupt status + BIT #$01 ; Test raster interrupt + BEQ NOT_VIC + + ; Handle VIC/raster interrupt + ; (your game logic here) + + LDA #$01 ; Acknowledge interrupt + STA $D019 + +NOT_VIC: + PLA ; Restore registers + TAY + PLA + TAX + PLA + RTI +``` + +--- + +## 6. Sprite Data Format + +C64 sprites are 24 pixels wide × 21 pixels high, stored as 63 bytes (3 bytes/line). + +**Location:** Default at $3000–$3FFF (can be changed via $D018) + +**Format:** +``` +Bytes 0–2: Line 0 (pixel data) +Bytes 3–5: Line 1 +... +Bytes 60–62: Line 20 +``` + +Each 3-byte line = 24 pixels (bit pattern). + +**Example: Simple square** +``` +$38 $38 $00 (11000000 11000000 00000000 = two vertical bars) +$38 $38 $00 +... +``` + +--- + +## 7. Useful Zero-Page Patterns + +```asm +; Temporary registers (safe to overwrite) +TEMP0 = $F0 +TEMP1 = $F1 +TEMP2 = $F2 +TEMP3 = $F3 + +; Loop counters +LOOP_X = $F4 +LOOP_Y = $F5 +LOOP_C = $F6 + +; Pointers (16-bit address) +PTR0 = $F8 ; PTR0_L = $F8, PTR0_H = $F9 +PTR1 = $FA +``` + +--- + +## 8. Quick Patterns + +### Wait for Raster (Sync to VIC) + +```asm +WAIT_RASTER: + LDA # Player input → select/move/attack + 1 => Animate move, resolve collision/damage + 2 => Enemy AI, animate enemy moves + 3 => Check win/lose, advance state + + ; 3. Render sprites & text + JSR SPRITE_UPDATE + JSR TEXT_UPDATE + + Restore registers + RTI +``` + +--- + +## 6. Subroutine Checklist + +### Input & Selection + +| Name | In | Out | Cost | +|------|----|----|------| +| `INPUT_READ` | — | `$30` (key code) | 10 cyc | +| `UNIT_SELECT` | `$30` (key) | `$32` (unit) | 20 cyc | +| `UNIT_MOVE` | `$32`, `$58`, `$59` | `$5A` (valid) | 40 cyc | +| `UNIT_ATTACK` | `$32`, target unit | HP update | 30 cyc | + +### Game Logic + +| Name | In | Out | Cost | +|------|----|----|------| +| `FIND_CLOSEST_ENEMY` | `$32` (player unit) | `$F0` (enemy idx) | 50 cyc | +| `ENEMY_MOVE` | `$F0` (enemy idx) | Update unit pos | 80 cyc | +| `ENEMY_AI` | — | Execute all enemies | 300 cyc | +| `CHECK_WIN` | — | Set `GAME_STATE` | 20 cyc | + +### Rendering + +| Name | In | Out | Cost | +|------|----|----|------| +| `SPRITE_UPDATE` | Unit state | VIC-II regs | 100 cyc | +| `TEXT_UPDATE` | Game state | Screen RAM | 50 cyc | +| `REBUILD_OCCUPANCY` | Unit state | Occupancy array | 60 cyc | + +--- + +## 7. Key Algorithms + +### Manhattan Distance + +```asm +; Input: X in $F0, Y in $F1 (target); A (own X), $F2 (own Y) +; Output: A (distance) +MANHATTAN: + SEC + SBC $F0 ; A = own_x - target_x + BPL + ; If positive, skip + EOR #$FF + ADC #$01 ; Negate (two's complement) ++ STA $F3 ; Save |dx| + + LDA $F2 + SEC + SBC $F1 ; A = own_y - target_y + BPL + ; If positive, skip + EOR #$FF + ADC #$01 ; Negate ++ CLC + ADC $F3 ; A = |dx| + |dy| + RTS +``` + +### Grid Occupancy Check + +```asm +; Input: X in $F0, Y in $F1 +; Output: A = unit index (or $FF if empty) +GRID_OCCUPIED: + LDA $F1 + ASL + ASL + ASL ; A = Y * 8 + CLC + ADC $F0 ; A = Y*8 + X + TAX + LDA $6000,X ; Lookup in occupancy array + RTS +``` + +--- + +## 8. State Transitions + +``` +WAITING_FOR_INPUT + ↓ [Player selects unit] + ↓ [Player moves/attacks] +EXECUTING_MOVE + ↓ [Move resolves, damage applied] +ENEMY_TURN + ↓ [Each enemy acts] + ↓ [Wait for animations] +CHECK_WIN + ↓ [If enemy count = 0 or player count = 0] +GAME_OVER (win/lose) + ↓ [Display result, wait for restart key] +WAITING_FOR_INPUT [loop] +``` + +--- + +## 9. Build & Run + +**Development tools:** +- **cc65** (C-to-6502 compiler, includes assembler `ca65` and linker `ld65`) +- **VICE** (Commodore 64 emulator, with debugger) +- **Makefile** or shell script to assemble & link + +**Command sequence:** +```bash +ca65 main.asm -o main.o # Assemble +ld65 main.o -C c64.cfg -o game.prg # Link +x64sc game.prg # Run in VICE +``` + +--- + +## 10. Implementation Order (MVP Priority) + +1. **Kernel & Interrupt Handler** — Get IRQ handler ticking +2. **Sprite Rendering** — Display 6 units on screen +3. **Grid & Occupancy** — Manage unit positions +4. **Input & Unit Selection** — Player can select a unit +5. **Movement & Collision** — Player can move units (no combat yet) +6. **Combat & Damage** — Attack resolves HP +7. **Enemy AI** — Simple greedy pathfinding +8. **Win/Lose Detection** — Game ends correctly +9. **Polish** — Text output, feedback, cleanup + +--- + +## 11. Known Pitfalls & Mitigations + +| Risk | Mitigation | +|------|-----------| +| Interrupt handler exceeds raster line | Use raster-stable jitter removal; keep subroutines cycle-predictable | +| Sprite overlap (>8 on screen) | Multiplex later; for now, 6 sprites fit without overlap | +| Occupancy array corruption | Rebuild every frame from unit state; don't write directly | +| AI hangs/slowdown | Limit enemy AI to 1 move each; use lookup tables for distance | +| Collision bugs | Test grid occupancy before allowing move; confirm unit dies at 0 HP | + +--- + +## 12. File Structure + +``` +skirmish-game/ + src/ + main.asm # Entry point, IRQ handler, main loop + input.asm # Input polling, key handling + game-logic.asm # Unit selection, movement, combat + enemy-ai.asm # Pathfinding, enemy turn + render.asm # Sprite updates, text output + data.asm # Lookup tables, sprite/tile data + assets/ + sprites.chr # C64 sprite design (binary) + tiles.chr # Tile data (if used) + docs/ + GDD.md # Game design (this file) + TECHNICAL-PLAN.md # Technical details (this file) + C64-CHEATSHEET.md # Quick register/memory reference + Makefile # Build script + c64.cfg # cc65 linker config (if using cc65) + README.md # Build & run instructions +``` + +--- + +## 13. Next Steps + +1. **Choose assembler:** cc65 + ca65/ld65 (recommended) or ACME +2. **Set up build environment** on your machine +3. **Write skeleton main.asm** (entry point + IRQ stub) +4. **Create sprite test** (render 6 colored squares) +5. **Implement zero-page variables** +6. **Add input handler** (keyboard or joystick) +7. **Iterate gameplay logic** + diff --git a/skirmish-game/enemy-ai.o b/skirmish-game/enemy-ai.o new file mode 100644 index 0000000000000000000000000000000000000000..923c31a5662ebf7ec330b705111bfa7be70385d2 GIT binary patch literal 5677 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 Rf;9BJnwD;@kmY|={{hjhwJ`ty literal 0 HcmV?d00001 diff --git a/skirmish-game/render.o b/skirmish-game/render.o new file mode 100644 index 0000000000000000000000000000000000000000..be89bbde42ff59d7343f5dc63b4213587f5da979 GIT binary patch literal 10462 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 f@ZLXz`(SCREEN_RAM + 40 * 0), >(SCREEN_RAM + 40 * 1), >(SCREEN_RAM + 40 * 2), >(SCREEN_RAM + 40 * 3), >(SCREEN_RAM + 40 * 4) + .byte >(SCREEN_RAM + 40 * 5), >(SCREEN_RAM + 40 * 6), >(SCREEN_RAM + 40 * 7), >(SCREEN_RAM + 40 * 8), >(SCREEN_RAM + 40 * 9) + .byte >(SCREEN_RAM + 40 * 10), >(SCREEN_RAM + 40 * 11), >(SCREEN_RAM + 40 * 12), >(SCREEN_RAM + 40 * 13), >(SCREEN_RAM + 40 * 14) + .byte >(SCREEN_RAM + 40 * 15), >(SCREEN_RAM + 40 * 16), >(SCREEN_RAM + 40 * 17), >(SCREEN_RAM + 40 * 18), >(SCREEN_RAM + 40 * 19) + .byte >(SCREEN_RAM + 40 * 20), >(SCREEN_RAM + 40 * 21), >(SCREEN_RAM + 40 * 22), >(SCREEN_RAM + 40 * 23), >(SCREEN_RAM + 40 * 24) + +color_row_lo: + .byte <(COLOR_RAM + 40 * 0), <(COLOR_RAM + 40 * 1), <(COLOR_RAM + 40 * 2), <(COLOR_RAM + 40 * 3), <(COLOR_RAM + 40 * 4) + .byte <(COLOR_RAM + 40 * 5), <(COLOR_RAM + 40 * 6), <(COLOR_RAM + 40 * 7), <(COLOR_RAM + 40 * 8), <(COLOR_RAM + 40 * 9) + .byte <(COLOR_RAM + 40 * 10), <(COLOR_RAM + 40 * 11), <(COLOR_RAM + 40 * 12), <(COLOR_RAM + 40 * 13), <(COLOR_RAM + 40 * 14) + .byte <(COLOR_RAM + 40 * 15), <(COLOR_RAM + 40 * 16), <(COLOR_RAM + 40 * 17), <(COLOR_RAM + 40 * 18), <(COLOR_RAM + 40 * 19) + .byte <(COLOR_RAM + 40 * 20), <(COLOR_RAM + 40 * 21), <(COLOR_RAM + 40 * 22), <(COLOR_RAM + 40 * 23), <(COLOR_RAM + 40 * 24) +color_row_hi: + .byte >(COLOR_RAM + 40 * 0), >(COLOR_RAM + 40 * 1), >(COLOR_RAM + 40 * 2), >(COLOR_RAM + 40 * 3), >(COLOR_RAM + 40 * 4) + .byte >(COLOR_RAM + 40 * 5), >(COLOR_RAM + 40 * 6), >(COLOR_RAM + 40 * 7), >(COLOR_RAM + 40 * 8), >(COLOR_RAM + 40 * 9) + .byte >(COLOR_RAM + 40 * 10), >(COLOR_RAM + 40 * 11), >(COLOR_RAM + 40 * 12), >(COLOR_RAM + 40 * 13), >(COLOR_RAM + 40 * 14) + .byte >(COLOR_RAM + 40 * 15), >(COLOR_RAM + 40 * 16), >(COLOR_RAM + 40 * 17), >(COLOR_RAM + 40 * 18), >(COLOR_RAM + 40 * 19) + .byte >(COLOR_RAM + 40 * 20), >(COLOR_RAM + 40 * 21), >(COLOR_RAM + 40 * 22), >(COLOR_RAM + 40 * 23), >(COLOR_RAM + 40 * 24) + +title_text: + .byte 19, 11, 9, 18, 13, 9, 19, 8, 0 +player_turn_text: + .byte 16, 12, 1, 25, 5, 18, 32, 20, 21, 18, 14, 0 +enemy_turn_text: + .byte 5, 14, 5, 13, 25, 32, 20, 8, 9, 14, 11, 9, 14, 7, 0 +win_text: + .byte 25, 15, 21, 32, 23, 9, 14, 0 +lose_text: + .byte 7, 1, 13, 5, 32, 15, 22, 5, 18, 0 +controls_text: + .byte 17, 32, 19, 5, 12, 32, 23, 1, 19, 4, 32, 13, 15, 22, 5, 32, 5, 32, 23, 1, 9, 20, 32, 18, 32, 18, 5, 19, 20, 1, 18, 20, 0 +turn_text: + .byte 20, 21, 18, 14, 32, 0 +select_text: + .byte 19, 5, 12, 32, 0 +player_label_text: + .byte 16, 0 +enemy_label_text: + .byte 5, 0 + +.segment "SPRITES" +.align 64 +player_sprite: + .byte $00, $18, $00 + .byte $00, $3C, $00 + .byte $00, $7E, $00 + .byte $00, $FF, $00 + .byte $01, $FF, $80 + .byte $03, $FF, $C0 + .byte $07, $FF, $E0 + .byte $0F, $FF, $F0 + .byte $1F, $FF, $F8 + .byte $3F, $FF, $FC + .byte $3F, $7E, $FC + .byte $1E, $3C, $78 + .byte $0C, $18, $30 + .byte $0C, $18, $30 + .byte $1E, $3C, $78 + .byte $3F, $24, $FC + .byte $33, $24, $CC + .byte $21, $24, $84 + .byte $01, $C3, $80 + .byte $00, $C3, $00 + .byte $00, $42, $00 + .byte $00 + +enemy_sprite: + .byte $00, $00, $00 + .byte $03, $C3, $C0 + .byte $07, $E7, $E0 + .byte $0F, $FF, $F0 + .byte $1E, $7E, $78 + .byte $3C, $3C, $3C + .byte $78, $18, $1E + .byte $F0, $00, $0F + .byte $E0, $00, $07 + .byte $C3, $81, $C3 + .byte $C7, $FF, $E3 + .byte $EF, $FF, $F7 + .byte $7F, $FF, $FE + .byte $3F, $FF, $FC + .byte $1F, $FF, $F8 + .byte $0F, $7E, $F0 + .byte $06, $24, $60 + .byte $0C, $3C, $30 + .byte $18, $24, $18 + .byte $30, $00, $0C + .byte $00, $00, $00 + .byte $00 diff --git a/skirmish-game/src/enemy-ai.asm b/skirmish-game/src/enemy-ai.asm new file mode 100644 index 0000000..4ec3f5a --- /dev/null +++ b/skirmish-game/src/enemy-ai.asm @@ -0,0 +1,303 @@ +.include "skirmish.inc" + +.export ENEMY_AI_TURN + +.import REBUILD_OCCUPANCY +.import UNIT_ATTACK +.import ENSURE_SELECTED_UNIT + +.importzp temp0 +.importzp temp1 +.importzp temp2 +.importzp temp3 +.importzp temp4 +.importzp temp5 +.importzp temp6 +.importzp temp7 + +.import unit_x +.import unit_y +.import unit_hp +.import unit_team +.import grid_occupancy + +.segment "CODE" +ENEMY_AI_TURN: + ldx #PLAYER_COUNT +enemy_loop: + cpx #UNIT_COUNT + beq enemy_done + lda unit_hp,x + beq next_enemy + + stx temp7 + jsr FIND_CLOSEST_PLAYER + cpy #$FF + beq restore_enemy + jsr ENEMY_ACT +restore_enemy: + ldx temp7 +next_enemy: + inx + jmp enemy_loop + +enemy_done: + jsr ENSURE_SELECTED_UNIT + rts + +FIND_CLOSEST_PLAYER: + ldx temp7 + lda unit_x,x + sta temp0 + lda unit_y,x + sta temp1 + + lda #$FF + sta temp2 + sta temp3 + ldy #$FF + + ldx #0 +find_target_loop: + lda unit_hp,x + beq next_target + + lda temp0 + cmp unit_x,x + bcs :+ + lda unit_x,x + sec + sbc temp0 + jmp :++ +: sec + sbc unit_x,x +: sta temp4 + + lda temp1 + cmp unit_y,x + bcs :+ + lda unit_y,x + sec + sbc temp1 + jmp :++ +: sec + sbc unit_y,x +: clc + adc temp4 + sta temp4 + + lda unit_x,x + cmp #3 + bcs :+ + sec + lda #3 + sbc unit_x,x + jmp :++ +: sec + sbc #3 +: sta temp5 + + lda unit_y,x + cmp #3 + bcs :+ + sec + lda #3 + sbc unit_y,x + jmp :++ +: sec + sbc #3 +: clc + adc temp5 + sta temp5 + + lda temp4 + cmp temp2 + bcc better_target + bne next_target + lda temp5 + cmp temp3 + bcs next_target + +better_target: + lda temp4 + sta temp2 + lda temp5 + sta temp3 + txa + tay + +next_target: + inx + cpx #PLAYER_COUNT + bne find_target_loop + rts + +ENEMY_ACT: + sty temp6 + ldx temp7 + + lda unit_x,x + sta temp2 + lda unit_y,x + sta temp3 + + ldy temp6 + lda unit_x,y + sta temp4 + lda unit_y,y + sta temp5 + + lda temp2 + cmp temp4 + bcs :+ + lda temp4 + sec + sbc temp2 + jmp :++ +: sec + sbc temp4 +: sta temp0 + + lda temp3 + cmp temp5 + bcs :+ + lda temp5 + sec + sbc temp3 + jmp :++ +: sec + sbc temp5 +: sta temp1 + + lda temp0 + clc + adc temp1 + cmp #1 + bne move_enemy + + ldx temp7 + ldy temp6 + jsr UNIT_ATTACK + jsr REBUILD_OCCUPANCY + rts + +move_enemy: + jsr CALC_X_STEP + sta temp2 + jsr CALC_Y_STEP + sta temp3 + + lda temp0 + cmp temp1 + bcc try_y_first + +try_x_first: + lda temp2 + beq try_y_after_x + jsr TRY_X_MOVE + bcs enemy_move_done +try_y_after_x: + lda temp3 + beq enemy_move_fail + jsr TRY_Y_MOVE + bcs enemy_move_done + jmp enemy_move_fail + +try_y_first: + lda temp3 + beq try_x_after_y + jsr TRY_Y_MOVE + bcs enemy_move_done +try_x_after_y: + lda temp2 + beq enemy_move_fail + jsr TRY_X_MOVE + bcs enemy_move_done + +enemy_move_fail: + clc + rts + +enemy_move_done: + jsr REBUILD_OCCUPANCY + sec + rts + +CALC_X_STEP: + ldx temp7 + ldy temp6 + lda unit_x,y + cmp unit_x,x + beq x_step_zero + bcc x_step_left + lda #1 + rts +x_step_left: + lda #$FF + rts +x_step_zero: + lda #0 + rts + +CALC_Y_STEP: + ldx temp7 + ldy temp6 + lda unit_y,y + cmp unit_y,x + beq y_step_zero + bcc y_step_up + lda #1 + rts +y_step_up: + lda #$FF + rts +y_step_zero: + lda #0 + rts + +TRY_X_MOVE: + ldx temp7 + lda unit_x,x + clc + adc temp2 + sta temp4 + cmp #8 + bcs move_fail + lda unit_y,x + sta temp5 + jmp TRY_MOVE_COMMON + +TRY_Y_MOVE: + ldx temp7 + lda unit_x,x + sta temp4 + lda unit_y,x + clc + adc temp3 + sta temp5 + cmp #8 + bcs move_fail + +TRY_MOVE_COMMON: + lda temp5 + asl + asl + asl + clc + adc temp4 + tay + lda grid_occupancy,y + cmp #EMPTY_CELL + bne move_fail + + ldx temp7 + lda temp4 + sta unit_x,x + lda temp5 + sta unit_y,x + sec + rts + +move_fail: + clc + rts diff --git a/skirmish-game/src/game-logic.asm b/skirmish-game/src/game-logic.asm new file mode 100644 index 0000000..178908b --- /dev/null +++ b/skirmish-game/src/game-logic.asm @@ -0,0 +1,391 @@ +.include "skirmish.inc" + +.export INIT_GAME +.export UPDATE_GAME +.export REBUILD_OCCUPANCY +.export UNIT_ATTACK +.export ENSURE_SELECTED_UNIT + +.import ENEMY_AI_TURN + +.importzp input_flags +.importzp game_state +.importzp selected_unit +.importzp turn_counter +.importzp player_units +.importzp enemy_units +.importzp move_dx +.importzp move_dy +.importzp action_result +.importzp temp0 +.importzp temp1 +.importzp temp2 +.importzp temp3 +.importzp temp4 +.importzp temp5 + +.import unit_x +.import unit_y +.import unit_hp +.import unit_team +.import grid_occupancy + +.segment "CODE" +INIT_GAME: + lda #STATE_PLAYER + sta game_state + lda #0 + sta turn_counter + sta action_result + lda #0 + sta selected_unit + + lda #PLAYER_COUNT + sta player_units + lda #ENEMY_COUNT + sta enemy_units + + ldx #0 +clear_units: + lda #0 + sta unit_x,x + sta unit_y,x + sta unit_hp,x + sta unit_team,x + inx + cpx #UNIT_COUNT + bne clear_units + + lda #0 + sta unit_x+0 + sta unit_x+1 + sta unit_x+2 + lda #7 + sta unit_x+3 + sta unit_x+4 + sta unit_x+5 + + lda #1 + sta unit_y+0 + lda #3 + sta unit_y+1 + lda #5 + sta unit_y+2 + lda #1 + sta unit_y+3 + lda #3 + sta unit_y+4 + lda #5 + sta unit_y+5 + + lda #3 + ldx #0 +hp_loop: + sta unit_hp,x + inx + cpx #UNIT_COUNT + bne hp_loop + + lda #TEAM_PLAYER + sta unit_team+0 + sta unit_team+1 + sta unit_team+2 + lda #TEAM_ENEMY + sta unit_team+3 + sta unit_team+4 + sta unit_team+5 + + jsr REBUILD_OCCUPANCY + rts + +UPDATE_GAME: + jsr REBUILD_OCCUPANCY + + lda game_state + cmp #STATE_PLAYER + beq update_player + cmp #STATE_ENEMY + beq update_enemy + cmp #STATE_CHECK + beq update_check + jmp update_end + +update_player: + jsr ENSURE_SELECTED_UNIT + + lda input_flags + and #INPUT_NEXT + beq :+ + jsr CYCLE_SELECTED_UNIT + rts +: + lda input_flags + and #INPUT_FIRE + beq :+ + jsr CYCLE_SELECTED_UNIT + rts +: + lda input_flags + and #INPUT_WAIT + beq :+ + lda #STATE_ENEMY + sta game_state + rts +: + lda #0 + sta move_dx + sta move_dy + + lda input_flags + and #INPUT_UP + beq :+ + lda #$FF + sta move_dy + jmp do_player_action +: + lda input_flags + and #INPUT_DOWN + beq :+ + lda #1 + sta move_dy + jmp do_player_action +: + lda input_flags + and #INPUT_LEFT + beq :+ + lda #$FF + sta move_dx + jmp do_player_action +: + lda input_flags + and #INPUT_RIGHT + beq :+ + lda #1 + sta move_dx + jmp do_player_action +: + rts + +do_player_action: + jsr UNIT_TRY_ACTION + lda action_result + beq :+ + lda #STATE_ENEMY + sta game_state +: + rts + +update_enemy: + jsr ENEMY_AI_TURN + inc turn_counter + lda #STATE_CHECK + sta game_state + rts + +update_check: + jsr CHECK_WIN_LOSE + lda game_state + cmp #STATE_CHECK + bne :+ + lda #STATE_PLAYER + sta game_state +: + rts + +update_end: + lda input_flags + and #INPUT_RESTART + beq :+ + jsr INIT_GAME +: + rts + +UNIT_TRY_ACTION: + lda #0 + sta action_result + + ldx selected_unit + cpx #PLAYER_COUNT + bcs unit_try_done + lda unit_hp,x + beq unit_try_done + + lda unit_x,x + clc + adc move_dx + sta temp0 + cmp #8 + bcs unit_try_done + + lda unit_y,x + clc + adc move_dy + sta temp1 + cmp #8 + bcs unit_try_done + + lda temp1 + asl + asl + asl + clc + adc temp0 + tay + lda grid_occupancy,y + cmp #EMPTY_CELL + beq move_player + + tay + lda unit_team,y + cmp #TEAM_ENEMY + bne unit_try_done + + ldx selected_unit + jsr UNIT_ATTACK_XY + lda #1 + sta action_result + jmp unit_try_done + +move_player: + ldx selected_unit + lda temp0 + sta unit_x,x + lda temp1 + sta unit_y,x + lda #1 + sta action_result + +unit_try_done: + rts + +REBUILD_OCCUPANCY: + ldx #0 +clear_grid: + lda #EMPTY_CELL + sta grid_occupancy,x + inx + cpx #64 + bne clear_grid + + ldx #0 +grid_loop: + lda unit_hp,x + beq next_grid_unit + lda unit_y,x + asl + asl + asl + clc + adc unit_x,x + tay + txa + sta grid_occupancy,y +next_grid_unit: + inx + cpx #UNIT_COUNT + bne grid_loop + rts + +CHECK_WIN_LOSE: + lda enemy_units + bne :+ + lda #STATE_WIN + sta game_state + rts +: + lda player_units + bne :+ + lda #STATE_LOSE + sta game_state + rts +: + lda #STATE_CHECK + sta game_state + rts + +ENSURE_SELECTED_UNIT: + lda selected_unit + cmp #PLAYER_COUNT + bcs choose_first_player + tax + lda unit_hp,x + bne ensure_done + +choose_first_player: + ldx #0 +find_alive_player: + lda unit_hp,x + bne found_player + inx + cpx #PLAYER_COUNT + bne find_alive_player + lda #$FF + sta selected_unit + rts + +found_player: + stx selected_unit +ensure_done: + rts + +CYCLE_SELECTED_UNIT: + lda player_units + beq no_cycle_target + + lda selected_unit + cmp #PLAYER_COUNT + bcc :+ + lda #$FF +: + clc + adc #1 + cmp #PLAYER_COUNT + bcc :+ + lda #0 +: + sta temp0 + ldx #PLAYER_COUNT +cycle_search: + ldy temp0 + lda unit_hp,y + bne cycle_found + inc temp0 + lda temp0 + cmp #PLAYER_COUNT + bcc :+ + lda #0 + sta temp0 +: + dex + bne cycle_search + +no_cycle_target: + lda #$FF + sta selected_unit + rts + +cycle_found: + sty selected_unit + rts + +UNIT_ATTACK: + jsr UNIT_ATTACK_XY + rts + +UNIT_ATTACK_XY: + lda unit_hp,y + beq attack_done + sec + sbc #1 + sta unit_hp,y + bne attack_done + lda unit_team,y + beq dead_player + dec enemy_units + jmp attack_done + +dead_player: + dec player_units + +attack_done: + rts diff --git a/skirmish-game/src/input.asm b/skirmish-game/src/input.asm new file mode 100644 index 0000000..2121cf9 --- /dev/null +++ b/skirmish-game/src/input.asm @@ -0,0 +1,112 @@ +.include "skirmish.inc" + +.export INPUT_READ + +.importzp input_flags +.importzp last_input_flags +.importzp temp0 +.importzp temp1 + +.segment "CODE" +INPUT_READ: + lda #0 + sta temp0 + + lda $DC00 + sta temp1 + + lda temp1 + and #$01 + bne :+ + lda temp0 + ora #INPUT_UP + sta temp0 +: + lda temp1 + and #$02 + bne :+ + lda temp0 + ora #INPUT_DOWN + sta temp0 +: + lda temp1 + and #$04 + bne :+ + lda temp0 + ora #INPUT_LEFT + sta temp0 +: + lda temp1 + and #$08 + bne :+ + lda temp0 + ora #INPUT_RIGHT + sta temp0 +: + lda temp1 + and #$10 + bne :+ + lda temp0 + ora #INPUT_FIRE + sta temp0 +: + jsr $FFE4 + beq keyboard_done + + cmp #'W' + bne :+ + lda temp0 + ora #INPUT_UP + sta temp0 +: + cmp #'S' + bne :+ + lda temp0 + ora #INPUT_DOWN + sta temp0 +: + cmp #'A' + bne :+ + lda temp0 + ora #INPUT_LEFT + sta temp0 +: + cmp #'D' + bne :+ + lda temp0 + ora #INPUT_RIGHT + sta temp0 +: + cmp #'Q' + bne :+ + lda temp0 + ora #INPUT_NEXT + sta temp0 +: + cmp #' ' + bne :+ + lda temp0 + ora #INPUT_WAIT + sta temp0 +: + cmp #'E' + bne :+ + lda temp0 + ora #INPUT_WAIT + sta temp0 +: + cmp #'R' + bne keyboard_done + lda temp0 + ora #INPUT_RESTART + sta temp0 + +keyboard_done: + lda last_input_flags + eor #$FF + and temp0 + sta input_flags + + lda temp0 + sta last_input_flags + rts diff --git a/skirmish-game/src/render.asm b/skirmish-game/src/render.asm new file mode 100644 index 0000000..d6040bf --- /dev/null +++ b/skirmish-game/src/render.asm @@ -0,0 +1,613 @@ +.include "skirmish.inc" + +.export INIT_RENDER +.export SPRITE_UPDATE +.export TEXT_UPDATE + +.importzp game_state +.importzp selected_unit +.importzp turn_counter +.importzp player_units +.importzp enemy_units +.importzp temp0 +.importzp temp1 +.importzp temp2 +.importzp temp3 +.importzp temp4 +.importzp temp5 + +.import unit_x +.import unit_y +.import unit_hp + +.import grid_x_low +.import grid_x_msb +.import grid_y_pos +.import sprite_bits +.import screen_row_lo +.import screen_row_hi +.import color_row_lo +.import color_row_hi + +.import title_text +.import player_turn_text +.import enemy_turn_text +.import win_text +.import lose_text +.import controls_text +.import turn_text +.import select_text +.import player_label_text +.import enemy_label_text +.import player_sprite +.import enemy_sprite + +.segment "CODE" +INIT_RENDER: + lda #BORDER_COLOR + sta $D020 + lda #BG_COLOR + sta $D021 + + lda #0 + sta $D015 + sta $D017 + sta $D01B + sta $D01C + sta $D01D + sta $D010 + + jsr CLEAR_SCREEN_AND_COLOR + jsr DRAW_STATIC_UI + jsr SETUP_SPRITES + rts + +SPRITE_UPDATE: + lda #0 + sta temp0 + sta temp1 + + ldx #0 +sprite_loop: + lda unit_hp,x + beq hide_sprite + + lda temp0 + ora sprite_bits,x + sta temp0 + + stx temp4 + txa + asl + tay + + lda unit_x,x + tax + lda grid_x_low,x + sta $D000,y + lda grid_x_msb,x + beq :+ + ldx temp4 + lda temp1 + ora sprite_bits,x + sta temp1 +: + ldx temp4 + lda unit_y,x + tax + lda grid_y_pos,x + sta $D001,y + + ldx temp4 + cpx #PLAYER_COUNT + bcs enemy_color + cpx selected_unit + bne player_color + lda #SPRITE_SELECTED_COLOR + bne store_color +player_color: + lda #SPRITE_PLAYER_COLOR + bne store_color +enemy_color: + lda #SPRITE_ENEMY_COLOR +store_color: + sta $D027,x +hide_sprite_after_pos: + jmp next_sprite + +hide_sprite: + stx temp4 + txa + asl + tay + lda #250 + sta $D001,y + ldx temp4 + +next_sprite: + inx + cpx #UNIT_COUNT + bne sprite_loop + + lda temp0 + sta $D015 + lda temp1 + sta $D010 + rts + +TEXT_UPDATE: + ldx #1 + jsr CLEAR_ROW + lda game_state + cmp #STATE_PLAYER + beq text_player + cmp #STATE_ENEMY + beq text_enemy + cmp #STATE_WIN + beq text_win + lda #lose_text + sta temp3 + ldx #1 + lda #14 + jmp draw_state_line + +text_player: + lda #player_turn_text + sta temp3 + ldx #1 + lda #13 + jmp draw_state_line + +text_enemy: + lda #enemy_turn_text + sta temp3 + ldx #1 + lda #7 + jmp draw_state_line + +text_win: + lda #win_text + sta temp3 + ldx #1 + lda #5 + +draw_state_line: + sta temp4 + jsr SET_SCREEN_PTR + lda #14 + jsr ADD_TO_SCREEN_PTR + jsr WRITE_STRING + ldx #1 + lda temp4 + jsr COLOR_FULL_ROW + + ldx #22 + jsr CLEAR_ROW + ldx #22 + jsr SET_SCREEN_PTR + lda #2 + jsr ADD_TO_SCREEN_PTR + lda #turn_text + sta temp3 + jsr WRITE_STRING + lda turn_counter + jsr WRITE_DECIMAL2 + + lda #14 + jsr ADD_TO_SCREEN_PTR + lda #select_text + sta temp3 + jsr WRITE_STRING + lda selected_unit + cmp #$FF + beq no_selection + clc + adc #1 + clc + adc #48 + ldy #0 + sta (temp0),y + jmp status_done +no_selection: + lda #45 + ldy #0 + sta (temp0),y +status_done: + ldx #22 + lda #HUD_COLOR + jsr COLOR_FULL_ROW + + ldx #23 + jsr CLEAR_ROW + ldx #23 + jsr SET_SCREEN_PTR + lda #2 + jsr ADD_TO_SCREEN_PTR + lda #16 + ldy #0 + sta (temp0),y + iny + lda #49 + sta (temp0),y + iny + lda #58 + sta (temp0),y + iny + ldx #0 + lda unit_hp,x + clc + adc #48 + sta (temp0),y + + iny + iny + lda #16 + sta (temp0),y + iny + lda #50 + sta (temp0),y + iny + lda #58 + sta (temp0),y + iny + ldx #1 + lda unit_hp,x + clc + adc #48 + sta (temp0),y + + iny + iny + lda #16 + sta (temp0),y + iny + lda #51 + sta (temp0),y + iny + lda #58 + sta (temp0),y + iny + ldx #2 + lda unit_hp,x + clc + adc #48 + sta (temp0),y + + iny + iny + lda #5 + sta (temp0),y + iny + lda #49 + sta (temp0),y + iny + lda #58 + sta (temp0),y + iny + ldx #3 + lda unit_hp,x + clc + adc #48 + sta (temp0),y + + iny + iny + lda #5 + sta (temp0),y + iny + lda #50 + sta (temp0),y + iny + lda #58 + sta (temp0),y + iny + ldx #4 + lda unit_hp,x + clc + adc #48 + sta (temp0),y + + iny + iny + lda #5 + sta (temp0),y + iny + lda #51 + sta (temp0),y + iny + lda #58 + sta (temp0),y + iny + ldx #5 + lda unit_hp,x + clc + adc #48 + sta (temp0),y + + ldx #23 + lda #HUD_COLOR + jsr COLOR_FULL_ROW + rts + +CLEAR_SCREEN_AND_COLOR: + ldx #0 +clear_pages: + lda #32 + sta SCREEN_RAM + $0000,x + sta SCREEN_RAM + $0100,x + sta SCREEN_RAM + $0200,x + sta SCREEN_RAM + $02E8,x + + lda #HUD_COLOR + sta COLOR_RAM + $0000,x + sta COLOR_RAM + $0100,x + sta COLOR_RAM + $0200,x + sta COLOR_RAM + $02E8,x + inx + cpx #$E8 + bne clear_pages + rts + +DRAW_STATIC_UI: + ldx #0 + jsr SET_SCREEN_PTR + lda #15 + jsr ADD_TO_SCREEN_PTR + lda #title_text + sta temp3 + jsr WRITE_STRING + ldx #0 + lda #7 + jsr COLOR_FULL_ROW + + ldx #24 + jsr CLEAR_ROW + ldx #24 + jsr SET_SCREEN_PTR + lda #1 + jsr ADD_TO_SCREEN_PTR + lda #controls_text + sta temp3 + jsr WRITE_STRING + ldx #24 + lda #3 + jsr COLOR_FULL_ROW + + jsr DRAW_BOARD + rts + +DRAW_BOARD: + ldx #0 +board_top_labels: + lda screen_row_lo + (BOARD_ROW - 1) + sta temp0 + lda screen_row_hi + (BOARD_ROW - 1) + sta temp1 + txa + asl + asl + clc + adc #BOARD_COL + tay + txa + clc + adc #48 + sta (temp0),y + + lda color_row_lo + (BOARD_ROW - 1) + sta temp2 + lda color_row_hi + (BOARD_ROW - 1) + sta temp3 + lda #BOARD_COLOR + sta (temp2),y + + txa + pha + txa + asl + clc + adc #BOARD_ROW + tax + jsr DRAW_BOARD_ROW + pla + tax + inx + cpx #8 + bne board_top_labels + rts + +DRAW_BOARD_ROW: + stx temp5 + lda screen_row_lo,x + sta temp0 + lda screen_row_hi,x + sta temp1 + lda color_row_lo,x + sta temp2 + lda color_row_hi,x + sta temp3 + + ldy #BOARD_COL + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + + lda temp5 + sec + sbc #BOARD_ROW + lsr + clc + adc #48 + ldy #2 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + rts + +SETUP_SPRITES: + lda #<(player_sprite / 64) + sta SPRITE_PTRS + 0 + sta SPRITE_PTRS + 1 + sta SPRITE_PTRS + 2 + lda #<(enemy_sprite / 64) + sta SPRITE_PTRS + 3 + sta SPRITE_PTRS + 4 + sta SPRITE_PTRS + 5 + rts + +CLEAR_ROW: + jsr SET_SCREEN_PTR + ldy #0 +clear_row_loop: + lda #32 + sta (temp0),y + iny + cpy #40 + bne clear_row_loop + rts + +SET_SCREEN_PTR: + lda screen_row_lo,x + sta temp0 + lda screen_row_hi,x + sta temp1 + rts + +ADD_TO_SCREEN_PTR: + clc + adc temp0 + sta temp0 + bcc :+ + inc temp1 +: rts + +WRITE_STRING: + ldy #0 +write_string_loop: + lda (temp2),y + beq write_string_done + sta (temp0),y + iny + bne write_string_loop +write_string_done: + tya + clc + adc temp0 + sta temp0 + bcc :+ + inc temp1 +: rts + +WRITE_DECIMAL2: + sta temp4 + ldx #0 +decimal_tens_loop: + lda temp4 + cmp #10 + bcc decimal_store + sec + sbc #10 + sta temp4 + inx + jmp decimal_tens_loop + +decimal_store: + ldy #0 + txa + clc + adc #48 + sta (temp0),y + iny + lda temp4 + clc + adc #48 + sta (temp0),y + iny + tya + clc + adc temp0 + sta temp0 + bcc :+ + inc temp1 +: rts + +COLOR_FULL_ROW: + sta temp4 + lda color_row_lo,x + sta temp2 + lda color_row_hi,x + sta temp3 + ldy #0 +color_loop: + lda temp4 + sta (temp2),y + iny + cpy #40 + bne color_loop + rts diff --git a/skirmish-game/src/skirmish.asm b/skirmish-game/src/skirmish.asm new file mode 100644 index 0000000..9238496 --- /dev/null +++ b/skirmish-game/src/skirmish.asm @@ -0,0 +1,136 @@ +.include "skirmish.inc" + +.exportzp input_flags +.exportzp last_input_flags +.exportzp game_state +.exportzp selected_unit +.exportzp turn_counter +.exportzp player_units +.exportzp enemy_units +.exportzp move_dx +.exportzp move_dy +.exportzp action_result +.exportzp frame_ready +.exportzp temp0 +.exportzp temp1 +.exportzp temp2 +.exportzp temp3 +.exportzp temp4 +.exportzp temp5 +.exportzp temp6 +.exportzp temp7 + +.export unit_x +.export unit_y +.export unit_hp +.export unit_team +.export old_irq +.export grid_occupancy + +.import INPUT_READ +.import INIT_GAME +.import UPDATE_GAME +.import INIT_RENDER +.import SPRITE_UPDATE +.import TEXT_UPDATE + +.segment "LOADADDR" + .word $0801 + +.segment "EXEHDR" + .byte $0B, $08, $0A, $00, $9E + .byte "2061", $00, $00, $00 + +.segment "ZEROPAGE" +input_flags: .res 1 +last_input_flags: .res 1 +game_state: .res 1 +selected_unit: .res 1 +turn_counter: .res 1 +player_units: .res 1 +enemy_units: .res 1 +move_dx: .res 1 +move_dy: .res 1 +action_result: .res 1 +frame_ready: .res 1 +temp0: .res 1 +temp1: .res 1 +temp2: .res 1 +temp3: .res 1 +temp4: .res 1 +temp5: .res 1 +temp6: .res 1 +temp7: .res 1 + +.segment "BSS" +unit_x: .res UNIT_COUNT +unit_y: .res UNIT_COUNT +unit_hp: .res UNIT_COUNT +unit_team:.res UNIT_COUNT +old_irq: .res 2 + +.segment "OCCUPANCY" +grid_occupancy: .res 64 + +.segment "CODE" +start: + sei + jsr $FF81 + + lda #0 + sta frame_ready + sta last_input_flags + + lda $0314 + sta old_irq + lda $0315 + sta old_irq+1 + + jsr INIT_RENDER + jsr INIT_GAME + jsr install_irq + cli + +main_loop: + lda frame_ready + beq main_loop + lda #0 + sta frame_ready + + jsr INPUT_READ + jsr UPDATE_GAME + jsr SPRITE_UPDATE + jsr TEXT_UPDATE + jmp main_loop + +install_irq: + lda #irq_handler + sta $0315 + + lda $D011 + and #$7F + sta $D011 + lda #$80 + sta $D012 + + lda #$01 + sta $D01A + + lda $DC0D + lda $DD0D + lda #$01 + sta $D019 + rts + +irq_handler: + lda $D019 + and #$01 + beq chain_irq + lda #$01 + sta $D019 + inc frame_ready + +chain_irq: + jmp (old_irq) diff --git a/skirmish-game/src/skirmish.inc b/skirmish-game/src/skirmish.inc new file mode 100644 index 0000000..20002ef --- /dev/null +++ b/skirmish-game/src/skirmish.inc @@ -0,0 +1,40 @@ +UNIT_COUNT = 6 +PLAYER_COUNT = 3 +ENEMY_COUNT = 3 + +EMPTY_CELL = $FF + +TEAM_PLAYER = 0 +TEAM_ENEMY = 1 + +STATE_PLAYER = 0 +STATE_ENEMY = 1 +STATE_CHECK = 2 +STATE_WIN = 3 +STATE_LOSE = 4 + +INPUT_UP = $01 +INPUT_DOWN = $02 +INPUT_LEFT = $04 +INPUT_RIGHT = $08 +INPUT_FIRE = $10 +INPUT_NEXT = $20 +INPUT_WAIT = $40 +INPUT_RESTART = $80 + +SCREEN_RAM = $0400 +COLOR_RAM = $D800 +SPRITE_PTRS = SCREEN_RAM + $03F8 + +BOARD_COL = 4 +BOARD_ROW = 4 +BOARD_PIXEL_X = 56 +BOARD_PIXEL_Y = 60 + +SPRITE_PLAYER_COLOR = 13 +SPRITE_SELECTED_COLOR = 7 +SPRITE_ENEMY_COLOR = 10 +HUD_COLOR = 1 +BOARD_COLOR = 15 +BG_COLOR = 6 +BORDER_COLOR = 14