From 8282f8e902524420183e5ae0badf3da4c6a9a108 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 3 Aug 2026 10:12:06 -0400 Subject: [PATCH] overkill sio cruft, not keeping. --- code/duffle/pad.h | 12 +- code/duffle/word_count.metadata.h | 3 + code/hello_joypad/gen/hello_joypad.macs.h | 14 + code/hello_joypad/gen/hello_joypad.offsets.h | 160 ++++ code/hello_joypad/hello_joypad.c | 47 +- code/hello_joypad/hello_joypad.tape.c | 734 ++++++++++++++++++- scripts/build_psyq.ps1 | 25 +- 7 files changed, 958 insertions(+), 37 deletions(-) diff --git a/code/duffle/pad.h b/code/duffle/pad.h index a121f60..d5ebb20 100644 --- a/code/duffle/pad.h +++ b/code/duffle/pad.h @@ -33,8 +33,6 @@ enum { #define pad0_(btn_id) (btn_id << Pad0) #define pad1_(btn_id) (btn_id << Pad1) -void pad_init(U4 mode) asm("PadInit"); -U4 pad_read(U4 id) asm("PadRead"); /* ============================================================ * SIO0 raw controller polling surface (Phase 1 scaffolding) @@ -130,10 +128,12 @@ enum { R_PadState = R_T7 atom_reg, /* caller-pinned PadState* */ R_PadStatus = R_T4 atom_reg, /* scratch for status reads */ R_PadCountdown = R_T5 atom_reg, /* scratch for countdown budget */ -#define R_PadSioBase_Code R_T6_Code -#define R_PadState_Code R_T7_Code -#define R_PadStatus_Code R_T4_Code -#define R_PadCountdown_Code R_T5_Code + R_DiagPinScratch = R_T3 atom_reg, /* raw_sio_pad_poll_20260802 — diag scratch address */ +#define R_PadSioBase_Code R_T6_Code +#define R_PadState_Code R_T7_Code +#define R_PadStatus_Code R_T4_Code +#define R_PadCountdown_Code R_T5_Code +#define R_DiagPinScratch_Code R_T3_Code }; /* Address of SIO0 block in KSEG1 (the PS1-side uncached mirror) */ diff --git a/code/duffle/word_count.metadata.h b/code/duffle/word_count.metadata.h index a538aa0..a4ca955 100644 --- a/code/duffle/word_count.metadata.h +++ b/code/duffle/word_count.metadata.h @@ -22,6 +22,7 @@ WORD_COUNT(call_reg, 1) WORD_COUNT(call_addr, 1) WORD_COUNT(branch_le_zero, 1) WORD_COUNT(branch_equal, 1) +WORD_COUNT(branch_ne, 1) WORD_COUNT(add_ui, 1) WORD_COUNT(set_lt_u, 1) WORD_COUNT(set_lt_s, 1) @@ -29,7 +30,9 @@ WORD_COUNT(set_lt_si, 1) WORD_COUNT(set_lt_ui, 1) WORD_COUNT(load_word, 1) WORD_COUNT(load_half_u, 1) +WORD_COUNT(load_byte_u, 1) WORD_COUNT(store_word, 1) +WORD_COUNT(store_byte, 1) WORD_COUNT(add_ui_self, 1) WORD_COUNT(add_u_self, 1) WORD_COUNT(add_u, 1) diff --git a/code/hello_joypad/gen/hello_joypad.macs.h b/code/hello_joypad/gen/hello_joypad.macs.h index b6e91a5..dc2b9b5 100644 --- a/code/hello_joypad/gen/hello_joypad.macs.h +++ b/code/hello_joypad/gen/hello_joypad.macs.h @@ -63,3 +63,17 @@ WORD_COUNT(mac_put_disp_env, 15) , mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port) WORD_COUNT(mac_put_draw_env, 48) +#define mac_pad_sio_write_pad_state(status_val, state_ptr_reg, scratch_reg) \ + add_ui(scratch_reg, R_0, status_val) \ +, store_word(scratch_reg, state_ptr_reg, O_(PadState,status)) /* FIX 2026-08-02: buttons = 0x0000FFFF = "no buttons pressed" in + * libetc convention. Build it with LUI + ORI so addiu does not + * sign-extend 0xFFFF to 0xFFFFFFFF. */ \ +, load_upper_i(scratch_reg, 0x0000) \ +, or_i(scratch_reg, scratch_reg, 0xFFFF) \ +, store_word(scratch_reg, state_ptr_reg, O_(PadState,buttons)) \ +, add_ui(scratch_reg, R_0, 0x80808080) \ +, store_word(scratch_reg, state_ptr_reg, O_(PadState,left_x)) \ +, add_ui(scratch_reg, R_0, 0) \ +, store_word(scratch_reg, state_ptr_reg, O_(PadState,attempt)) +WORD_COUNT(mac_pad_sio_write_pad_state, 9) + diff --git a/code/hello_joypad/gen/hello_joypad.offsets.h b/code/hello_joypad/gen/hello_joypad.offsets.h index c09d58c..3ceff60 100644 --- a/code/hello_joypad/gen/hello_joypad.offsets.h +++ b/code/hello_joypad/gen/hello_joypad.offsets.h @@ -35,5 +35,165 @@ enum { atom_offset_bounds_chk_floor_f3_face_exit = _atom_offset_bounds_chk_floor_f3_face_exit, }; +// --- atom: pad_sio_step (398 words) --- + +#define _atom_offset_settle_pre_port0_settle_pre_port0 -3 +#define _atom_offset_settle_post_port0_settle_post_port0 -3 +#define _atom_offset_wait_ack0_port0_ack0_received_port0 12 +#define _atom_offset_continue_wait_ack0_port0_wait_ack0_port0 -6 +#define _atom_offset_skip_port0_from_ack0_port1_start 160 +#define _atom_offset_wait_ackrel0_port0_ack_released_port0 12 +#define _atom_offset_continue_wait_ackrel0_port0_wait_ackrel0_port0 -6 +#define _atom_offset_skip_port0_from_ackrel0_port1_start 142 +#define _atom_offset_wait_ack1_port0_ack1_received_port0 12 +#define _atom_offset_continue_wait_ack1_port0_wait_ack1_port0 -6 +#define _atom_offset_skip_port0_from_ack1_port1_start 123 +#define _atom_offset_wait_ackrel1_port0_ack_released1_port0 12 +#define _atom_offset_continue_wait_ackrel1_port0_wait_ackrel1_port0 -6 +#define _atom_offset_skip_port0_from_ackrel1_port1_start 105 +#define _atom_offset_wait_ack2_port0_ack2_received_port0 12 +#define _atom_offset_continue_wait_ack2_port0_wait_ack2_port0 -6 +#define _atom_offset_skip_port0_from_ack2_port1_start 86 +#define _atom_offset_wait_ackrel2_port0_ack_released2_port0 12 +#define _atom_offset_continue_wait_ackrel2_port0_wait_ackrel2_port0 -6 +#define _atom_offset_skip_port0_from_ackrel2_port1_start 68 +#define _atom_offset_wait_ack3_port0_ack3_received_port0 12 +#define _atom_offset_continue_wait_ack3_port0_wait_ack3_port0 -6 +#define _atom_offset_skip_port0_from_ack3_port1_start 49 +#define _atom_offset_wait_ackrel3_port0_ack_released3_port0 12 +#define _atom_offset_continue_wait_ackrel3_port0_wait_ackrel3_port0 -6 +#define _atom_offset_skip_port0_from_ackrel3_port1_start 31 +#define _atom_offset_wait_rx4_port0_rx4_received_port0 12 +#define _atom_offset_continue_wait_rx4_port0_wait_rx4_port0 -6 +#define _atom_offset_skip_port0_from_rx4_port1_start 12 +#define _atom_offset_settle_pre_port1_settle_pre_port1 -3 +#define _atom_offset_settle_post_port1_settle_post_port1 -3 +#define _atom_offset_wait_ack0_port1_ack0_received_port1 12 +#define _atom_offset_continue_wait_ack0_port1_wait_ack0_port1 -6 +#define _atom_offset_skip_port1_from_ack0_end_atom 160 +#define _atom_offset_wait_ackrel0_port1_ack_released_port1 12 +#define _atom_offset_continue_wait_ackrel0_port1_wait_ackrel0_port1 -6 +#define _atom_offset_skip_port1_from_ackrel0_end_atom 142 +#define _atom_offset_wait_ack1_port1_ack1_received_port1 12 +#define _atom_offset_continue_wait_ack1_port1_wait_ack1_port1 -6 +#define _atom_offset_skip_port1_from_ack1_end_atom 123 +#define _atom_offset_wait_ackrel1_port1_ack_released1_port1 12 +#define _atom_offset_continue_wait_ackrel1_port1_wait_ackrel1_port1 -6 +#define _atom_offset_skip_port1_from_ackrel1_end_atom 105 +#define _atom_offset_wait_ack2_port1_ack2_received_port1 12 +#define _atom_offset_continue_wait_ack2_port1_wait_ack2_port1 -6 +#define _atom_offset_skip_port1_from_ack2_end_atom 86 +#define _atom_offset_wait_ackrel2_port1_ack_released2_port1 12 +#define _atom_offset_continue_wait_ackrel2_port1_wait_ackrel2_port1 -6 +#define _atom_offset_skip_port1_from_ackrel2_end_atom 68 +#define _atom_offset_wait_ack3_port1_ack3_received_port1 12 +#define _atom_offset_continue_wait_ack3_port1_wait_ack3_port1 -6 +#define _atom_offset_skip_port1_from_ack3_end_atom 49 +#define _atom_offset_wait_ackrel3_port1_ack_released3_port1 12 +#define _atom_offset_continue_wait_ackrel3_port1_wait_ackrel3_port1 -6 +#define _atom_offset_skip_port1_from_ackrel3_end_atom 31 +#define _atom_offset_wait_rx4_port1_rx4_received_port1 12 +#define _atom_offset_continue_wait_rx4_port1_wait_rx4_port1 -6 +#define _atom_offset_skip_port1_from_rx4_end_atom 12 + +enum { + atom_offset_settle_pre_port0_settle_pre_port0 = _atom_offset_settle_pre_port0_settle_pre_port0, + atom_offset_settle_post_port0_settle_post_port0 = _atom_offset_settle_post_port0_settle_post_port0, + atom_offset_wait_ack0_port0_ack0_received_port0 = _atom_offset_wait_ack0_port0_ack0_received_port0, + atom_offset_continue_wait_ack0_port0_wait_ack0_port0 = _atom_offset_continue_wait_ack0_port0_wait_ack0_port0, + atom_offset_skip_port0_from_ack0_port1_start = _atom_offset_skip_port0_from_ack0_port1_start, + atom_offset_wait_ackrel0_port0_ack_released_port0 = _atom_offset_wait_ackrel0_port0_ack_released_port0, + atom_offset_continue_wait_ackrel0_port0_wait_ackrel0_port0 = _atom_offset_continue_wait_ackrel0_port0_wait_ackrel0_port0, + atom_offset_skip_port0_from_ackrel0_port1_start = _atom_offset_skip_port0_from_ackrel0_port1_start, + atom_offset_wait_ack1_port0_ack1_received_port0 = _atom_offset_wait_ack1_port0_ack1_received_port0, + atom_offset_continue_wait_ack1_port0_wait_ack1_port0 = _atom_offset_continue_wait_ack1_port0_wait_ack1_port0, + atom_offset_skip_port0_from_ack1_port1_start = _atom_offset_skip_port0_from_ack1_port1_start, + atom_offset_wait_ackrel1_port0_ack_released1_port0 = _atom_offset_wait_ackrel1_port0_ack_released1_port0, + atom_offset_continue_wait_ackrel1_port0_wait_ackrel1_port0 = _atom_offset_continue_wait_ackrel1_port0_wait_ackrel1_port0, + atom_offset_skip_port0_from_ackrel1_port1_start = _atom_offset_skip_port0_from_ackrel1_port1_start, + atom_offset_wait_ack2_port0_ack2_received_port0 = _atom_offset_wait_ack2_port0_ack2_received_port0, + atom_offset_continue_wait_ack2_port0_wait_ack2_port0 = _atom_offset_continue_wait_ack2_port0_wait_ack2_port0, + atom_offset_skip_port0_from_ack2_port1_start = _atom_offset_skip_port0_from_ack2_port1_start, + atom_offset_wait_ackrel2_port0_ack_released2_port0 = _atom_offset_wait_ackrel2_port0_ack_released2_port0, + atom_offset_continue_wait_ackrel2_port0_wait_ackrel2_port0 = _atom_offset_continue_wait_ackrel2_port0_wait_ackrel2_port0, + atom_offset_skip_port0_from_ackrel2_port1_start = _atom_offset_skip_port0_from_ackrel2_port1_start, + atom_offset_wait_ack3_port0_ack3_received_port0 = _atom_offset_wait_ack3_port0_ack3_received_port0, + atom_offset_continue_wait_ack3_port0_wait_ack3_port0 = _atom_offset_continue_wait_ack3_port0_wait_ack3_port0, + atom_offset_skip_port0_from_ack3_port1_start = _atom_offset_skip_port0_from_ack3_port1_start, + atom_offset_wait_ackrel3_port0_ack_released3_port0 = _atom_offset_wait_ackrel3_port0_ack_released3_port0, + atom_offset_continue_wait_ackrel3_port0_wait_ackrel3_port0 = _atom_offset_continue_wait_ackrel3_port0_wait_ackrel3_port0, + atom_offset_skip_port0_from_ackrel3_port1_start = _atom_offset_skip_port0_from_ackrel3_port1_start, + atom_offset_wait_rx4_port0_rx4_received_port0 = _atom_offset_wait_rx4_port0_rx4_received_port0, + atom_offset_continue_wait_rx4_port0_wait_rx4_port0 = _atom_offset_continue_wait_rx4_port0_wait_rx4_port0, + atom_offset_skip_port0_from_rx4_port1_start = _atom_offset_skip_port0_from_rx4_port1_start, + atom_offset_settle_pre_port1_settle_pre_port1 = _atom_offset_settle_pre_port1_settle_pre_port1, + atom_offset_settle_post_port1_settle_post_port1 = _atom_offset_settle_post_port1_settle_post_port1, + atom_offset_wait_ack0_port1_ack0_received_port1 = _atom_offset_wait_ack0_port1_ack0_received_port1, + atom_offset_continue_wait_ack0_port1_wait_ack0_port1 = _atom_offset_continue_wait_ack0_port1_wait_ack0_port1, + atom_offset_skip_port1_from_ack0_end_atom = _atom_offset_skip_port1_from_ack0_end_atom, + atom_offset_wait_ackrel0_port1_ack_released_port1 = _atom_offset_wait_ackrel0_port1_ack_released_port1, + atom_offset_continue_wait_ackrel0_port1_wait_ackrel0_port1 = _atom_offset_continue_wait_ackrel0_port1_wait_ackrel0_port1, + atom_offset_skip_port1_from_ackrel0_end_atom = _atom_offset_skip_port1_from_ackrel0_end_atom, + atom_offset_wait_ack1_port1_ack1_received_port1 = _atom_offset_wait_ack1_port1_ack1_received_port1, + atom_offset_continue_wait_ack1_port1_wait_ack1_port1 = _atom_offset_continue_wait_ack1_port1_wait_ack1_port1, + atom_offset_skip_port1_from_ack1_end_atom = _atom_offset_skip_port1_from_ack1_end_atom, + atom_offset_wait_ackrel1_port1_ack_released1_port1 = _atom_offset_wait_ackrel1_port1_ack_released1_port1, + atom_offset_continue_wait_ackrel1_port1_wait_ackrel1_port1 = _atom_offset_continue_wait_ackrel1_port1_wait_ackrel1_port1, + atom_offset_skip_port1_from_ackrel1_end_atom = _atom_offset_skip_port1_from_ackrel1_end_atom, + atom_offset_wait_ack2_port1_ack2_received_port1 = _atom_offset_wait_ack2_port1_ack2_received_port1, + atom_offset_continue_wait_ack2_port1_wait_ack2_port1 = _atom_offset_continue_wait_ack2_port1_wait_ack2_port1, + atom_offset_skip_port1_from_ack2_end_atom = _atom_offset_skip_port1_from_ack2_end_atom, + atom_offset_wait_ackrel2_port1_ack_released2_port1 = _atom_offset_wait_ackrel2_port1_ack_released2_port1, + atom_offset_continue_wait_ackrel2_port1_wait_ackrel2_port1 = _atom_offset_continue_wait_ackrel2_port1_wait_ackrel2_port1, + atom_offset_skip_port1_from_ackrel2_end_atom = _atom_offset_skip_port1_from_ackrel2_end_atom, + atom_offset_wait_ack3_port1_ack3_received_port1 = _atom_offset_wait_ack3_port1_ack3_received_port1, + atom_offset_continue_wait_ack3_port1_wait_ack3_port1 = _atom_offset_continue_wait_ack3_port1_wait_ack3_port1, + atom_offset_skip_port1_from_ack3_end_atom = _atom_offset_skip_port1_from_ack3_end_atom, + atom_offset_wait_ackrel3_port1_ack_released3_port1 = _atom_offset_wait_ackrel3_port1_ack_released3_port1, + atom_offset_continue_wait_ackrel3_port1_wait_ackrel3_port1 = _atom_offset_continue_wait_ackrel3_port1_wait_ackrel3_port1, + atom_offset_skip_port1_from_ackrel3_end_atom = _atom_offset_skip_port1_from_ackrel3_end_atom, + atom_offset_wait_rx4_port1_rx4_received_port1 = _atom_offset_wait_rx4_port1_rx4_received_port1, + atom_offset_continue_wait_rx4_port1_wait_rx4_port1 = _atom_offset_continue_wait_rx4_port1_wait_rx4_port1, + atom_offset_skip_port1_from_rx4_end_atom = _atom_offset_skip_port1_from_rx4_end_atom, +}; + +// --- atom: pad_apply_input (60 words) --- + +#define _atom_offset_dpad_left_exit_dpad_left 6 +#define _atom_offset_dpad_right_exit_dpad_right 6 +#define _atom_offset_dead_zone_low_check_dead_low_active 8 +#define _atom_offset_dead_zone_high_check_dead_high_active 15 +#define _atom_offset_dead_zone_skip_exit_stick 23 +#define _atom_offset_end_low_exit_stick 11 + +enum { + atom_offset_dpad_left_exit_dpad_left = _atom_offset_dpad_left_exit_dpad_left, + atom_offset_dpad_right_exit_dpad_right = _atom_offset_dpad_right_exit_dpad_right, + atom_offset_dead_zone_low_check_dead_low_active = _atom_offset_dead_zone_low_check_dead_low_active, + atom_offset_dead_zone_high_check_dead_high_active = _atom_offset_dead_zone_high_check_dead_high_active, + atom_offset_dead_zone_skip_exit_stick = _atom_offset_dead_zone_skip_exit_stick, + atom_offset_end_low_exit_stick = _atom_offset_end_low_exit_stick, +}; + +// --- atom: pad_sio_diag_byte_exchange (53 words) --- + +#define _atom_offset_diag_wait_ack0_diag_ack0_done 5 +#define _atom_offset_diag_wait_ack0_diag_wait_ack0 -6 +#define _atom_offset_diag_timeout_ack0_diag_timeout 24 +#define _atom_offset_diag_wait_ack1_diag_ack1_done 5 +#define _atom_offset_diag_wait_ack1_diag_wait_ack1 -6 +#define _atom_offset_diag_timeout_ack1_diag_timeout 11 +#define _atom_offset_diag_success_diag_done 7 + +enum { + atom_offset_diag_wait_ack0_diag_ack0_done = _atom_offset_diag_wait_ack0_diag_ack0_done, + atom_offset_diag_wait_ack0_diag_wait_ack0 = _atom_offset_diag_wait_ack0_diag_wait_ack0, + atom_offset_diag_timeout_ack0_diag_timeout = _atom_offset_diag_timeout_ack0_diag_timeout, + atom_offset_diag_wait_ack1_diag_ack1_done = _atom_offset_diag_wait_ack1_diag_ack1_done, + atom_offset_diag_wait_ack1_diag_wait_ack1 = _atom_offset_diag_wait_ack1_diag_wait_ack1, + atom_offset_diag_timeout_ack1_diag_timeout = _atom_offset_diag_timeout_ack1_diag_timeout, + atom_offset_diag_success_diag_done = _atom_offset_diag_success_diag_done, +}; + #pragma endregion hello_joypad.tape diff --git a/code/hello_joypad/hello_joypad.c b/code/hello_joypad/hello_joypad.c index 868c7e7..0c396d7 100644 --- a/code/hello_joypad/hello_joypad.c +++ b/code/hello_joypad/hello_joypad.c @@ -125,10 +125,14 @@ typedef Struct_(SMemory) { U4 pad_state; + PadState pad[2]; /* raw_sio_pad_poll_20260802 — per-port */ + PadSioInit pad_sio_init; /* raw_sio_pad_poll_20260802 — boot init */ + U4_V scratchpad; // d-cache }; global SMemory smem; extern SMemory smem; +U4 scratch_for_atom_diag_pin; /* raw_sio_pad_poll_20260802 — diag atom scratch */ #define pad0_signal_(btn_id) smem.pad_state & pad0_(btn_id) #define pad1_signal_(btn_id) smem.pad_state & pad1_(btn_id) @@ -201,8 +205,6 @@ void update(PrimitiveArena* pa, U4* ordering_buf) { TapeBuilder tb = tb_make(slice_ut_arr(smem.MemTape)); - smem.pad_state = pad_read(0); - if (0) // Pad Input { if (pad0_signal_(Pad_Left)) { @@ -217,8 +219,16 @@ void update(PrimitiveArena* pa, U4* ordering_buf) if (1) // Pad Input (Tape version) { tb.used = 0; tb_scope_run(& tb) { - tb_emit(& tb, pad_input_demo); - tb_data(& tb, smem.pad_state); + /* Per-frame SIO0 poll: pad_sio_step polls both ports; pad_apply_input applies rotation */ + tb_emit(& tb, pad_sio_init); + tb_emit(& tb, pad_sio_step); + tb_data(& tb, u4_(& smem.pad[0])); + tb_data(& tb, u4_(& smem.pad[1])); + tb_data(& tb, u4_(smem.pad_sio_init.sio_base_addr[0])); + tb_data(& tb, u4_(smem.pad_sio_init.sio_base_addr[1])); + /* Per-frame rotation apply: consume pad[0].buttons + pad[0].left_x */ + tb_emit(& tb, pad_apply_input); + tb_data(& tb, u4_(& smem.pad[0])); tb_data(& tb, u4_(& smem.cube.rot)); tb_data(& tb, u4_(& smem.floor.rot)); } @@ -454,13 +464,26 @@ int main(void) } TapeBuilder tb = tb_make(slice_ut_arr(smem.MemTape)); { reset_graph(0); - register U4* io_base_addr rgcc(R_IO_BaseAddr) = u4_r(IO_BASE_ADDR); - register DoubleBuffer* screen_buf rgcc(R_ScreenBuf) = & smem.screen_buf; + pad_sio_init_setup(& smem.pad_sio_init, & smem.pad[0], & smem.pad[1]); + /* raw_sio_pad_poll_20260802 — pin the SIO base for the boot atom. + * Use the KSEG1 constant directly (not via pad_sio_init.sio_base_addr[0]) + * because rgcc binds the register to the variable's storage, not the + * value-at-call-site. Reading the field at tape-emit time would require + * an extra load, which the atom body can't do implicitly. */ + register U4 pad_io_base rgcc(R_PadSioBase) = pad_IO_KSEG1_BASE; + register U4* io_base_addr rgcc(R_IO_BaseAddr) = u4_r(IO_BASE_ADDR); + register U4* r_diag_scratch rgcc(R_DiagPinScratch) = & scratch_for_atom_diag_pin; + register DoubleBuffer* screen_buf rgcc(R_ScreenBuf) = & smem.screen_buf; tb.used = 0; tb_scope_run(& tb) { tb_emit(& tb, screen_env_init); tb_emit(& tb, gp_screen_init); + tb_emit(& tb, pad_sio_init); + tb_emit(& tb, pad_sio_step); /* initial baseline */ + tb_data(& tb, u4_(& smem.pad[0])); + tb_data(& tb, u4_(& smem.pad[1])); + tb_data(& tb, u4_(smem.pad_sio_init.sio_base_addr[0])); + tb_data(& tb, u4_(smem.pad_sio_init.sio_base_addr[1])); } - pad_init(0); } while (1) { gknown S4* active_buf_id = & smem.active_buf_id; @@ -472,3 +495,13 @@ int main(void) }; return 0; } + +/* raw_sio_pad_poll_20260802 — populate the boot-time pad SIO context. + * Populates the two PadState pointers + the KSEG1 SIO base address. */ +void pad_sio_init_setup(PadSioInit* init, PadState* s0, PadState* s1) +{ + init->sio_base_addr[0] = pad_IO_KSEG1_BASE; + init->sio_base_addr[1] = pad_IO_KSEG1_BASE; + init->pad_state_ptr[0] = s0; + init->pad_state_ptr[1] = s1; +} diff --git a/code/hello_joypad/hello_joypad.tape.c b/code/hello_joypad/hello_joypad.tape.c index 22b1c8a..d0fa8e6 100644 --- a/code/hello_joypad/hello_joypad.tape.c +++ b/code/hello_joypad/hello_joypad.tape.c @@ -2,11 +2,11 @@ # include "duffle/gen/duffle.macs.h" # include "duffle/gen/duffle.offsets.h" # include "duffle/atom_dsl.h" -# include "duffle/pad.h" # include "duffle/lottes_tape.h" # include "duffle/mips.h" # include "duffle/gte.h" # include "duffle/gp.h" +# include "duffle/pad.h" # include "duffle/word_count.metadata.h" # include "psyq.h" # include "gen/hello_joypad.offsets.h" @@ -106,6 +106,36 @@ MipsAtomComp_Proc_(ac_put_draw_env, { mac_gcmd_push(gp0_word_nop(), reg_transfer, reg_base, port), }) +/* ac_pad_sio_write_pad_state + * raw_sio_pad_poll_20260802 — Task 3.1 helper component. + * Writes the per-port PadState in 5 instructions plus 4 store_word calls (status, + * buttons, left_x/y/right_x/right_y packed, attempt). The provisional decode publishes + * 0x0000FFFF buttons + centered axes on every path until response-byte decode lands. + * + * Args: + * status_val - the PadSioStatus enum value to publish + * state_ptr_reg - the PadState* base (R_PadState at the call site) + * scratch_reg - scratch register for the value being stored (e.g., R_T0) + * + * Emits 9 instructions (status/buttons/axes/attempt stores plus the + * two-instruction zero-extended buttons load). + */ +FI_ MipsAtom ac_pad_sio_write_pad_state(U4 status_val, U4 state_ptr_reg, U4 scratch_reg) +MipsAtomComp_Proc_(ac_pad_sio_write_pad_state, { + add_ui(scratch_reg, R_0, status_val), + store_word(scratch_reg, state_ptr_reg, O_(PadState,status)), + /* FIX 2026-08-02: buttons = 0x0000FFFF = "no buttons pressed" in + * libetc convention. Build it with LUI + ORI so addiu does not + * sign-extend 0xFFFF to 0xFFFFFFFF. */ + load_upper_i(scratch_reg, 0x0000), + or_i(scratch_reg, scratch_reg, 0xFFFF), + store_word(scratch_reg, state_ptr_reg, O_(PadState,buttons)), + add_ui(scratch_reg, R_0, 0x80808080), + store_word(scratch_reg, state_ptr_reg, O_(PadState,left_x)), + add_ui(scratch_reg, R_0, 0), + store_word(scratch_reg, state_ptr_reg, O_(PadState,attempt)) +}) + #pragma endregion MACs #pragma region Baked Atoms @@ -382,32 +412,724 @@ internal MipsAtom_(sync_primitive_arena) atom_info(atom_bind(Binds_SyncPrimitive * (Phase 2 fills the body.) */ internal MipsAtom_(pad_sio_init) atom_info(atom_phase(pad_init) -, atom_reads(R_T5, R_PadSioBase) -, atom_writes(R_T5) +, atom_reads(R_T5, R_T6) +, atom_writes(R_T5, R_T6) ) { + /* FIX 2026-08-02: explicitly load the KSEG1 base into R_T6 at the top of + * the atom body. The rgcc(R_PadSioBase) binding in main() pins R_T6 = base + * when main() runs, but $12 is caller-saved per the O32 ABI — when tape_run + * is invoked, R_T6 is fair game. The atom body cannot rely on the value. */ + load_upper_i(R_T6, pad_IO_KSEG1_BASE >> 16), /* R_T6 high 16 = 0xBF80 */ + or_i(R_T6, R_T6, pad_IO_KSEG1_BASE & 0xFFFF), /* R_T6 = 0xBF800000 */ + + /* SIO CTRL = 0x0040 (reset) */ + add_ui(R_T5, R_0, pad_SIO_CTRL_RESET), + store_half(R_T5, R_T6, pad_SIO_CTRL_OFFSET), + /* SIO MODE = 0x000D (MUL1, 8-bit, no parity, idle-high) */ + add_ui(R_T5, R_0, pad_SIO_MODE_INIT), + store_half(R_T5, R_T6, pad_SIO_MODE_OFFSET), + /* SIO BAUD = 0x0088 (~250 kHz) */ + add_ui(R_T5, R_0, pad_SIO_BAUD_INIT), + store_half(R_T5, R_T6, pad_SIO_BAUD_OFFSET), mac_yield(), }; /* ----- pad_sio_step ----- * Per-frame bounded raw-SIO transaction. Reads PadState pointers + SIO * base addresses from Binds_PadSioStep; writes per-port status + - * buttons + axes into smem.pad[0..1]. (Phase 3 fills the body.) + * buttons + axes into smem.pad[0..1]. + * + * Body shape (per spec §"Transaction model (per port, per pad_sio_step)"): + * port 0: CTRL=CLEANUP → settle → CTRL=port-select → settle → exchange 5 + * bytes (addr + 0x42 0x00 0x00 0x00) → decode → write PadState[0] + * → CTRL=CLEANUP. + * port 1: swap scratch regs (sio_base_addr1 → R_PadSioBase, state1 → + * R_PadState) → mirror port 0 sequence. + * + * Bounded-loop semantics: every countdown is wrapped in + * add_ui_self(R_T1, -1) + branch_ne(R_T1, R_0, ...) + * with a known maximum (pad_SIO_SETTLE_BEFORE_TX=1000, pad_SIO_SETTLE_AFTER_TX=2000, + * pad_SIO_WAIT_BUDGET=4096). The static-analysis pass currently reports + * has_loops = true; the follow-up metaprogram track that learns modeled-bounded + * loops is out of scope here (per spec §"Risks"). + * + * Scratch register strategy: + * R_PadStatus = R_T4 — RESERVED for port-1 swap (holds state1) + * R_PadCountdown = R_T5 — RESERVED for port-1 swap (holds sio_base_addr1) + * R_T0 — byte-exchange value + STAT read (clobbered freely) + * R_T1 — countdown budget (clobbered freely) + * R_PadState = R_T7 — PadState* (preserved for PadState writes) + * R_PadSioBase = R_T6 — SIO base (preserved through the port) + * + * Response decode (Task 3.1 teaching scope): + * - status = PadSioStatus_Digital (hardcoded) + * - buttons = 0xFFFF (no buttons pressed in the provisional libetc + * convention; full response-byte decode is follow-up) + * - axes = 0x80808080 (centered: left_x=0x80, left_y=0x80, + * right_x=0x80, right_y=0x80) + * - attempt = 0 + * - DualShock handshake (0x43 0x01 → 0x44 0x01 0x03 → 0x43 0x00) is + * follow-up scope; the hardcoded digital decode is a placeholder. + * + * Both ports raise /CS (CTRL = pad_SIO_CTRL_CLEANUP) before exit. Both ports + * treat response timeout as PadSioStatus_Disconnected per the spec §"Failure + * handling" + the canonical per-port timeout semantics. */ internal MipsAtom_(pad_sio_step) atom_info(atom_bind(Binds_PadSioStep) , atom_reads(R_TapePtr, R_PadSioBase, R_PadState, R_PadStatus, R_PadCountdown) , atom_writes(R_PadStatus, R_PadCountdown) ) { + /* FIX 2026-08-02: explicitly load KSEG1 base into R_PadSioBase (R_T6) at the + * top. The rgcc() binding in main() does NOT survive the tape_run call + * because R_T6 is caller-saved per the O32 ABI. The pad_sio_init atom + * (also in the per-frame tape) reloads R_T6 separately. */ + load_upper_i(R_PadSioBase, pad_IO_KSEG1_BASE >> 16), + or_i(R_PadSioBase, R_PadSioBase, pad_IO_KSEG1_BASE & 0xFFFF), + + /* Pop Binds from tape (in Binds_PadSioStep declaration order) */ + load_word(R_PadState, R_TapePtr, O_(Binds_PadSioStep,state0)), + load_word(R_PadStatus, R_TapePtr, O_(Binds_PadSioStep,state1)), /* reserved for port-1 swap */ + load_word(R_PadSioBase, R_TapePtr, O_(Binds_PadSioStep,sio_base_addr0)), + load_word(R_PadCountdown, R_TapePtr, O_(Binds_PadSioStep,sio_base_addr1)), /* reserved for port-1 swap */ + add_ui_self(R_TapePtr, S_(Binds_PadSioStep)), + + /* ============== PORT 0 TRANSACTION ============== */ + /* Use R_T0 (byte value / STAT read) + R_T1 (countdown) as scratch. + * R_PadStatus (state1) + R_PadCountdown (sio_base_addr1) are preserved + * through the port-0 body and swapped into R_PadSioBase + R_PadState + * at atom_offset(port1_start, ...) below. */ + + /* 1. Cleanup: CTRL = 0x0010 (raise /CS, clear stale status) */ + add_ui(R_T0, R_0, pad_SIO_CTRL_CLEANUP), + store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET), + /* Bounded by pad_SIO_SETTLE_BEFORE_TX = 1000 iterations. */ + add_ui(R_T1, R_0, pad_SIO_SETTLE_BEFORE_TX), +atom_label(settle_pre_port0) + nop, /* BD slot */ + add_ui_self(R_T1, -1), + branch_ne(R_T1, R_0, atom_offset(settle_pre_port0, settle_pre_port0)), + + /* 2. Port-select: CTRL = 0x0003 (TX enable + DTR /CS) for port 0 */ + add_ui(R_T0, R_0, pad_SIO_CTRL_TX_ENABLE), + or_i(R_T0, R_T0, pad_SIO_CTRL_DTR_CS), /* set /CS line low */ + store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET), + /* Bounded by pad_SIO_SETTLE_AFTER_TX = 2000 iterations. */ + add_ui(R_T1, R_0, pad_SIO_SETTLE_AFTER_TX), +atom_label(settle_post_port0) + nop, + add_ui_self(R_T1, -1), + branch_ne(R_T1, R_0, atom_offset(settle_post_port0, settle_post_port0)), + + /* 3. Address byte (0x01) — send + RX-ready wait + read response + RX-drain confirmation */ + add_ui(R_T0, R_0, pad_PROTO_ADDR), + store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ack0_port0) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_ne(R_T0, R_0, atom_offset(wait_ack0_port0, ack0_received_port0)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ack0_port0) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ack0_port0, wait_ack0_port0)), + /* RX timeout → mark disconnected; skip to port 1 */ + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port0_from_ack0) + branch_equal(R_0, R_0, atom_offset(skip_port0_from_ack0, port1_start)), + +atom_label(ack0_received_port0) + /* Read open-bus response byte 0 — discard per docs/psx-spx §controllersandmemorycards.md */ + load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + + /* Confirm RX FIFO drained before sending byte 1. Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ackrel0_port0) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_equal(R_T0, R_0, atom_offset(wait_ackrel0_port0, ack_released_port0)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ackrel0_port0) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel0_port0, wait_ackrel0_port0)), + /* RX-drain timeout → disconnected; skip to port 1 */ + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port0_from_ackrel0) + branch_equal(R_0, R_0, atom_offset(skip_port0_from_ackrel0, port1_start)), + +atom_label(ack_released_port0) + + /* === Byte 1 (port 0): send 0x42 (cmd read) + RX-ready wait + read response + RX-drain confirmation === */ + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T0, R_0, pad_PROTO_CMD_READ), + store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ack1_port0) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_ne(R_T0, R_0, atom_offset(wait_ack1_port0, ack1_received_port0)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ack1_port0) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ack1_port0, wait_ack1_port0)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port0_from_ack1) + branch_equal(R_0, R_0, atom_offset(skip_port0_from_ack1, port1_start)), + +atom_label(ack1_received_port0) + /* Read response ID byte — discarded for teaching scope (decode hardcoded). */ + load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + + /* RX FIFO drain wait. Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ackrel1_port0) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_equal(R_T0, R_0, atom_offset(wait_ackrel1_port0, ack_released1_port0)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ackrel1_port0) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel1_port0, wait_ackrel1_port0)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port0_from_ackrel1) + branch_equal(R_0, R_0, atom_offset(skip_port0_from_ackrel1, port1_start)), + +atom_label(ack_released1_port0) + + /* === Byte 2 (port 0): send 0x00 + RX-ready wait + read response + RX-drain confirmation === */ + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T0, R_0, 0x00), + store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ack2_port0) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_ne(R_T0, R_0, atom_offset(wait_ack2_port0, ack2_received_port0)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ack2_port0) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ack2_port0, wait_ack2_port0)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port0_from_ack2) + branch_equal(R_0, R_0, atom_offset(skip_port0_from_ack2, port1_start)), + +atom_label(ack2_received_port0) + load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ackrel2_port0) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_equal(R_T0, R_0, atom_offset(wait_ackrel2_port0, ack_released2_port0)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ackrel2_port0) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel2_port0, wait_ackrel2_port0)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port0_from_ackrel2) + branch_equal(R_0, R_0, atom_offset(skip_port0_from_ackrel2, port1_start)), + +atom_label(ack_released2_port0) + + /* === Byte 3 (port 0): send 0x00 + RX-ready wait + read response + RX-drain confirmation === */ + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T0, R_0, 0x00), + store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ack3_port0) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_ne(R_T0, R_0, atom_offset(wait_ack3_port0, ack3_received_port0)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ack3_port0) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ack3_port0, wait_ack3_port0)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port0_from_ack3) + branch_equal(R_0, R_0, atom_offset(skip_port0_from_ack3, port1_start)), + +atom_label(ack3_received_port0) + load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ackrel3_port0) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_equal(R_T0, R_0, atom_offset(wait_ackrel3_port0, ack_released3_port0)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ackrel3_port0) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel3_port0, wait_ackrel3_port0)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port0_from_ackrel3) + branch_equal(R_0, R_0, atom_offset(skip_port0_from_ackrel3, port1_start)), + +atom_label(ack_released3_port0) + + /* === Byte 4 (FINAL, port 0): send 0x00 + RX-not-empty wait + read final byte === */ + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T0, R_0, 0x00), + store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_rx4_port0) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_ne(R_T0, R_0, atom_offset(wait_rx4_port0, rx4_received_port0)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_rx4_port0) + branch_ne(R_T1, R_0, atom_offset(continue_wait_rx4_port0, wait_rx4_port0)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port0_from_rx4) + branch_equal(R_0, R_0, atom_offset(skip_port0_from_rx4, port1_start)), + +atom_label(rx4_received_port0) + load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), /* discard final byte */ + + /* === RESPONSE DECODE (hardcoded for teaching scope) === + * Per the plan §"Phase 3 task 3.1" + spec §"Architecture": + * - Full decode (buttons/axes from response bytes) is follow-up scope. + * - Teaching scope: hardcode digital poll response. + * status = PadSioStatus_Digital + * buttons = 0x0000FFFF (no buttons pressed — placeholder) + * axes = 0x80808080 (left_x=0x80, left_y=0x80, right_x=0x80, right_y=0x80) + * attempt = 0 + */ +atom_label(decode_port0) + mac_pad_sio_write_pad_state(PadSioStatus_Digital, R_PadState, R_T0), + + /* /CS cleanup: raise /CS, clear stale status before exiting port 0. */ + add_ui(R_T0, R_0, pad_SIO_CTRL_CLEANUP), + store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET), + + /* ============== PORT 1 SETUP ============== */ + /* Swap: R_PadCountdown holds sio_base_addr1; R_PadStatus holds state1. */ +atom_label(port1_start) + add_u(R_PadSioBase, R_0, R_PadCountdown), /* sio_base_addr1 → R_PadSioBase */ + add_u(R_PadState, R_0, R_PadStatus), /* state1 → R_PadState */ + + /* ============== PORT 1 TRANSACTION (mirror of port 0) ============== */ + /* R_PadStatus + R_PadCountdown are no longer reserved (port 1 is the + * last transaction); we still use R_T0/R_T1 as scratch to match port 0. */ + + /* 1. Cleanup: CTRL = 0x0010 (raise /CS, clear stale status) */ + add_ui(R_T0, R_0, pad_SIO_CTRL_CLEANUP), + store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET), + /* Bounded by pad_SIO_SETTLE_BEFORE_TX = 1000 iterations. */ + add_ui(R_T1, R_0, pad_SIO_SETTLE_BEFORE_TX), +atom_label(settle_pre_port1) + nop, + add_ui_self(R_T1, -1), + branch_ne(R_T1, R_0, atom_offset(settle_pre_port1, settle_pre_port1)), + + /* 2. Port-select: CTRL = 0x0003 | (1 << 13) (port 1 select) */ + add_ui(R_T0, R_0, pad_SIO_CTRL_TX_ENABLE), + or_i(R_T0, R_T0, pad_SIO_CTRL_DTR_CS), + or_i(R_T0, R_T0, 1 << 13), /* port 1 select bit (CTRL bit 13 = port select) */ + store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET), + /* Bounded by pad_SIO_SETTLE_AFTER_TX = 2000 iterations. */ + add_ui(R_T1, R_0, pad_SIO_SETTLE_AFTER_TX), +atom_label(settle_post_port1) + nop, + add_ui_self(R_T1, -1), + branch_ne(R_T1, R_0, atom_offset(settle_post_port1, settle_post_port1)), + + /* 3. Address byte (0x01) — send + RX-ready wait + read response + RX-drain confirmation */ + add_ui(R_T0, R_0, pad_PROTO_ADDR), + store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ack0_port1) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_ne(R_T0, R_0, atom_offset(wait_ack0_port1, ack0_received_port1)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ack0_port1) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ack0_port1, wait_ack0_port1)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port1_from_ack0) + branch_equal(R_0, R_0, atom_offset(skip_port1_from_ack0, end_atom)), + +atom_label(ack0_received_port1) + load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ackrel0_port1) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_equal(R_T0, R_0, atom_offset(wait_ackrel0_port1, ack_released_port1)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ackrel0_port1) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel0_port1, wait_ackrel0_port1)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port1_from_ackrel0) + branch_equal(R_0, R_0, atom_offset(skip_port1_from_ackrel0, end_atom)), + +atom_label(ack_released_port1) + + /* === Byte 1 (port 1): send 0x42 (cmd read) + RX-ready wait + read response + RX-drain confirmation === */ + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T0, R_0, pad_PROTO_CMD_READ), + store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ack1_port1) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_ne(R_T0, R_0, atom_offset(wait_ack1_port1, ack1_received_port1)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ack1_port1) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ack1_port1, wait_ack1_port1)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port1_from_ack1) + branch_equal(R_0, R_0, atom_offset(skip_port1_from_ack1, end_atom)), + +atom_label(ack1_received_port1) + load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ackrel1_port1) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_equal(R_T0, R_0, atom_offset(wait_ackrel1_port1, ack_released1_port1)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ackrel1_port1) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel1_port1, wait_ackrel1_port1)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port1_from_ackrel1) + branch_equal(R_0, R_0, atom_offset(skip_port1_from_ackrel1, end_atom)), + +atom_label(ack_released1_port1) + + /* === Byte 2 (port 1): send 0x00 + RX-ready wait + read response + RX-drain confirmation === */ + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T0, R_0, 0x00), + store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ack2_port1) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_ne(R_T0, R_0, atom_offset(wait_ack2_port1, ack2_received_port1)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ack2_port1) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ack2_port1, wait_ack2_port1)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port1_from_ack2) + branch_equal(R_0, R_0, atom_offset(skip_port1_from_ack2, end_atom)), + +atom_label(ack2_received_port1) + load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ackrel2_port1) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_equal(R_T0, R_0, atom_offset(wait_ackrel2_port1, ack_released2_port1)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ackrel2_port1) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel2_port1, wait_ackrel2_port1)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port1_from_ackrel2) + branch_equal(R_0, R_0, atom_offset(skip_port1_from_ackrel2, end_atom)), + +atom_label(ack_released2_port1) + + /* === Byte 3 (port 1): send 0x00 + RX-ready wait + read response + RX-drain confirmation === */ + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T0, R_0, 0x00), + store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ack3_port1) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_ne(R_T0, R_0, atom_offset(wait_ack3_port1, ack3_received_port1)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ack3_port1) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ack3_port1, wait_ack3_port1)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port1_from_ack3) + branch_equal(R_0, R_0, atom_offset(skip_port1_from_ack3, end_atom)), + +atom_label(ack3_received_port1) + load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_ackrel3_port1) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_equal(R_T0, R_0, atom_offset(wait_ackrel3_port1, ack_released3_port1)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_ackrel3_port1) + branch_ne(R_T1, R_0, atom_offset(continue_wait_ackrel3_port1, wait_ackrel3_port1)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port1_from_ackrel3) + branch_equal(R_0, R_0, atom_offset(skip_port1_from_ackrel3, end_atom)), + +atom_label(ack_released3_port1) + + /* === Byte 4 (FINAL, port 1): send 0x00 + RX-not-empty wait + read final byte === */ + /* Bounded by pad_SIO_WAIT_BUDGET = 4096 iterations. */ + add_ui(R_T0, R_0, 0x00), + store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(wait_rx4_port1) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_ne(R_T0, R_0, atom_offset(wait_rx4_port1, rx4_received_port1)), + add_ui_self(R_T1, -1), +atom_label(continue_wait_rx4_port1) + branch_ne(R_T1, R_0, atom_offset(continue_wait_rx4_port1, wait_rx4_port1)), + mac_pad_sio_write_pad_state(PadSioStatus_Disconnected, R_PadState, R_T0), +atom_label(skip_port1_from_rx4) + branch_equal(R_0, R_0, atom_offset(skip_port1_from_rx4, end_atom)), + +atom_label(rx4_received_port1) + load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), /* discard final byte */ + + /* === RESPONSE DECODE (port 1) === */ +atom_label(decode_port1) + mac_pad_sio_write_pad_state(PadSioStatus_Digital, R_PadState, R_T0), + + /* /CS cleanup: raise /CS, clear stale status before exiting port 1. */ + add_ui(R_T0, R_0, pad_SIO_CTRL_CLEANUP), + store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET), + +atom_label(end_atom) mac_yield(), }; /* ----- pad_apply_input ----- * Reads pad[0].buttons + pad[0].left_x; applies the input-semantics - * deltas to cube_rot.y + floor_rot.y. (Phase 4 fills the body.) + * deltas to cube_rot.y + floor_rot.y per spec §"Input semantics": + * - D-pad Left: cube_rot.y += 30, floor_rot.y += 5 + * - D-pad Right: cube_rot.y -= 30, floor_rot.y -= 5 + * - Analog stick X (dead zone 0x70..0x90): + * cube delta = (0x80 - left_x) >> 2 (range approx -32..+32) + * floor delta = (0x80 - left_x) >> 5 (range approx -4..+4) + * - D-pad + analog deltas add when used together. + * + * Convention (per spec line 137 + Task 3.1b buttons-fix): + * pad_state = 0 means no buttons active. The fail-safe zero-button + * value flows through unchanged, so a disconnected/fresh pad + * produces no rotation. The branch_le_zero pattern below matches + * the existing pad_input_demo convention (atom body lines 248/257). + * + * Signed-delta trick: load_byte_u zero-extends left_x to 32 bits; sub_u + * from 0x80 wraps to a SIGNED two's-complement value in the negative + * range; shift_aright (sra) then correctly sign-extends the shift for + * both positive (left_x < 0x80) and negative (left_x > 0x80) cases. + * Digital pads publish left_x = 0x80 → delta = 0 → no rotation, so the + * analog step is naturally a no-op for digital controllers. */ internal MipsAtom_(pad_apply_input) atom_info(atom_bind(Binds_PadApplyInput) -, atom_reads(R_T0, R_T1, R_T2, R_T5) +, atom_reads(R_T0, R_T1, R_T2, R_T3, R_T4, R_T5, R_TapePtr) , atom_writes(R_T1, R_T2) ) { + /* Pop Binds from tape (state, cube_rot, floor_rot) */ + load_word(R_T5, R_TapePtr, O_(Binds_PadApplyInput,state)), + load_word(R_T1, R_TapePtr, O_(Binds_PadApplyInput,cube_rot)), + load_word(R_T2, R_TapePtr, O_(Binds_PadApplyInput,floor_rot)), + add_ui_self(R_TapePtr, S_(Binds_PadApplyInput)), + + /* Load pad[0].buttons into R_T0. The following nop retires the MIPS-I + * load delay before the first and_i consumes R_T0. */ + load_word(R_T0, R_T5, O_(PadState,buttons)), + nop, + + /* D-pad Left: cube_rot.y += 30, floor_rot.y += 5. + * branch_le_zero SKIPS the apply block when the button bit is + * NOT set in R_T0 (matches pad_input_demo convention). */ + and_i(R_T3, R_T0, pad0_(Pad_Left)), + branch_le_zero(R_T3, atom_offset(dpad_left, exit_dpad_left)), + load_half( R_T4, R_T1, O_(V3_S2,y)), /* BD-slot */ + load_half( R_T3, R_T2, O_(V3_S2,y)), + add_si( R_T4, R_T4, 30), + add_si( R_T3, R_T3, 5), + store_half(R_T4, R_T1, O_(V3_S2,y)), + store_half(R_T3, R_T2, O_(V3_S2,y)), + atom_label(exit_dpad_left) + + /* D-pad Right: cube_rot.y -= 30, floor_rot.y -= 5. */ + and_i(R_T3, R_T0, pad0_(Pad_Right)), + branch_le_zero(R_T3, atom_offset(dpad_right, exit_dpad_right)), + load_half( R_T4, R_T1, O_(V3_S2,y)), /* BD-slot */ + load_half( R_T3, R_T2, O_(V3_S2,y)), + add_si( R_T4, R_T4, -30), + add_si( R_T3, R_T3, -5), + store_half(R_T4, R_T1, O_(V3_S2,y)), + store_half(R_T3, R_T2, O_(V3_S2,y)), + atom_label(exit_dpad_right) + + /* Analog left-stick X: dead zone 0x70..0x90 (per spec line 89). + * Cube delta = (0x80 - left_x) >> 2; floor delta = (0x80 - left_x) >> 5. */ + load_byte_u(R_T3, R_T5, O_(PadState,left_x)), + + /* Dead-zone check: skip analog if left_x in [0x70, 0x90] inclusive. + * Outside dead zone on LOW side: left_x < 0x70 (strictly). + * set_lt_u(R_T4, R_T3, R_T4=0x70) → R_T4 = (left_x < 0x70) ? 1 : 0. */ + add_ui(R_T4, R_0, 0x70), + set_lt_u(R_T4, R_T3, R_T4), + branch_ne(R_T4, R_0, atom_offset(dead_zone_low_check, dead_low_active)), + add_ui(R_T4, R_0, 0x80), /* BD-slot: pre-load 0x80 for dead_low_active */ + +atom_label(dead_check_upper) + /* left_x >= 0x70 → check upper bound. */ + load_byte_u(R_T3, R_T5, O_(PadState,left_x)), /* reload */ + add_ui(R_T4, R_0, 0x90), + set_lt_u(R_T4, R_T4, R_T3), /* R_T4 = (0x90 < left_x) ? 1 : 0 → (left_x > 0x90) ? 1 : 0 */ + branch_ne(R_T4, R_0, atom_offset(dead_zone_high_check, dead_high_active)), + add_ui(R_T4, R_0, 0x80), /* BD-slot: pre-load 0x80 for dead_high_active */ + /* Fall-through = left_x in [0x70, 0x90] (dead zone); skip analog entirely. */ + branch_equal(R_0, R_0, atom_offset(dead_zone_skip, exit_stick)), + nop, + +atom_label(dead_low_active) + /* R_T4 = 0x80 (from first BD-slot). delta = 0x80 - left_x (positive). */ + load_byte_u(R_T3, R_T5, O_(PadState,left_x)), + sub_u(R_T3, R_T4, R_T3), /* R_T3 = 0x80 - left_x */ + shift_aright(R_T4, R_T3, 2), /* R_T4 = cube_delta */ + load_half(R_T0, R_T1, O_(V3_S2,y)), + add_u(R_T0, R_T0, R_T4), + store_half(R_T0, R_T1, O_(V3_S2,y)), + shift_aright(R_T4, R_T3, 5), /* R_T4 = floor_delta */ + load_half(R_T0, R_T2, O_(V3_S2,y)), + add_u(R_T0, R_T0, R_T4), + store_half(R_T0, R_T2, O_(V3_S2,y)), + branch_equal(R_0, R_0, atom_offset(end_low, exit_stick)), + nop, + +atom_label(dead_high_active) + /* R_T4 = 0x80 (from second BD-slot). delta = 0x80 - left_x (signed negative). */ + load_byte_u(R_T3, R_T5, O_(PadState,left_x)), + sub_u(R_T3, R_T4, R_T3), + shift_aright(R_T4, R_T3, 2), /* R_T4 = cube_delta (signed) */ + load_half(R_T0, R_T1, O_(V3_S2,y)), + add_u(R_T0, R_T0, R_T4), + store_half(R_T0, R_T1, O_(V3_S2,y)), + shift_aright(R_T4, R_T3, 5), /* R_T4 = floor_delta (signed) */ + load_half(R_T0, R_T2, O_(V3_S2,y)), + add_u(R_T0, R_T0, R_T4), + store_half(R_T0, R_T2, O_(V3_S2,y)), +atom_label(exit_stick) + + mac_yield(), +}; + +/* ----- pad_sio_diag_pin ----- + * Per-frame diagnostic counter. The caller binds R_DiagPinScratch to + * scratch_for_atom_diag_pin for temporary gdb verification. + */ +internal MipsAtom_(pad_sio_diag_pin) atom_info(atom_phase(pad_init) +, atom_reads(R_T0, R_T1, R_DiagPinScratch) +, atom_writes(R_T0, R_T1, R_DiagPinScratch) +) { + /* FIX 2026-08-02: explicitly reload R_DiagPinScratch (R_T3 = $t3). Caller-saved + * per O32 ABI; the rgcc binding in main() does not survive tape_run. */ + load_upper_i(R_DiagPinScratch, 0x8001), + or_i(R_DiagPinScratch, R_DiagPinScratch, 0xC800), + + /* High half = 0xD1A6; low half increments once per atom invocation. */ + load_word(R_T1, R_DiagPinScratch, 0), + nop, + add_ui(R_T1, R_T1, 1), + and_i(R_T0, R_T1, 0xFFFF), + load_upper_i(R_T1, 0xD1A6), + or_i(R_T1, R_T1, 0), + or(R_T1, R_T1, R_T0), + store_word(R_T1, R_DiagPinScratch, 0), + mac_yield(), +}; + +/* ----- pad_sio_diag_byte_exchange ----- + * Temporary two-byte wire probe: sends 0x01 and 0x42, then stores the + * open-bus byte and response ID in scratch_for_atom_diag_pin. + */ +internal MipsAtom_(pad_sio_diag_byte_exchange) atom_info(atom_phase(pad_init) +, atom_reads(R_T0, R_T1, R_T2, R_PadSioBase, R_DiagPinScratch) +, atom_writes(R_T0, R_T1, R_T2, R_PadSioBase, R_DiagPinScratch) +) { + /* FIX 2026-08-02: explicitly reload R_DiagPinScratch (R_T3 = $t3). Caller-saved + * per O32 ABI; the rgcc binding in main() does not survive tape_run. */ + load_upper_i(R_DiagPinScratch, 0x8001), + or_i(R_DiagPinScratch, R_DiagPinScratch, 0xC800), + + /* FIX 2026-08-02: explicitly load KSEG1 base into R_PadSioBase (R_T6) at the + * top. The rgcc() binding in main() does NOT survive the tape_run call + * because R_T6 is caller-saved per the O32 ABI. */ + load_upper_i(R_PadSioBase, pad_IO_KSEG1_BASE >> 16), + or_i(R_PadSioBase, R_PadSioBase, pad_IO_KSEG1_BASE & 0xFFFF), + + add_ui(R_T0, R_0, pad_SIO_CTRL_CLEANUP), + store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET), + add_ui(R_T0, R_0, pad_SIO_CTRL_TX_ENABLE), + or_i(R_T0, R_T0, pad_SIO_CTRL_DTR_CS), + store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET), + + add_ui(R_T0, R_0, pad_PROTO_ADDR), + store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(diag_wait_ack0) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_ne(R_T0, R_0, atom_offset(diag_wait_ack0, diag_ack0_done)), + add_ui_self(R_T1, -1), + branch_ne(R_T1, R_0, atom_offset(diag_wait_ack0, diag_wait_ack0)), + add_ui(R_T0, R_0, 0xDEADAC01), + store_word(R_T0, R_DiagPinScratch, 0), + branch_equal(R_0, R_0, atom_offset(diag_timeout_ack0, diag_timeout)), +atom_label(diag_ack0_done) + load_byte_u(R_T2, R_PadSioBase, pad_SIO_DATA_OFFSET), + add_ui(R_T0, R_0, pad_PROTO_CMD_READ), + store_byte(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + add_ui(R_T1, R_0, pad_SIO_WAIT_BUDGET), +atom_label(diag_wait_ack1) + load_half_u(R_T0, R_PadSioBase, pad_SIO_STAT_OFFSET), + nop, + and_i(R_T0, R_T0, pad_SIO_STAT_RX_NOT_EMPTY), + branch_ne(R_T0, R_0, atom_offset(diag_wait_ack1, diag_ack1_done)), + add_ui_self(R_T1, -1), + branch_ne(R_T1, R_0, atom_offset(diag_wait_ack1, diag_wait_ack1)), + add_ui(R_T0, R_0, 0xDEADAC02), + store_word(R_T0, R_DiagPinScratch, 0), + branch_equal(R_0, R_0, atom_offset(diag_timeout_ack1, diag_timeout)), +atom_label(diag_ack1_done) + load_byte_u(R_T0, R_PadSioBase, pad_SIO_DATA_OFFSET), + nop, + shift_lleft(R_T0, R_T0, 8), + or(R_T2, R_T2, R_T0), + store_word(R_T2, R_DiagPinScratch, 0), +atom_label(diag_success) + branch_equal(R_0, R_0, atom_offset(diag_success, diag_done)), + nop, +atom_label(diag_timeout_ack0) + add_ui(R_T0, R_0, 0xDEADAC01), + store_word(R_T0, R_DiagPinScratch, 0), +atom_label(diag_timeout_ack1) + add_ui(R_T0, R_0, 0xDEADAC02), + store_word(R_T0, R_DiagPinScratch, 0), +atom_label(diag_timeout) + add_ui(R_T0, R_0, 0xDEADACFF), + store_word(R_T0, R_DiagPinScratch, 0), +atom_label(diag_done) + add_ui(R_T0, R_0, pad_SIO_CTRL_CLEANUP), + store_half(R_T0, R_PadSioBase, pad_SIO_CTRL_OFFSET), mac_yield(), }; diff --git a/scripts/build_psyq.ps1 b/scripts/build_psyq.ps1 index cea53fc..b3ee1ed 100644 --- a/scripts/build_psyq.ps1 +++ b/scripts/build_psyq.ps1 @@ -180,29 +180,18 @@ function link-modules { param([string[]]$link_modules, [string] $elf, [string[] $link_args += ($f_link_pass_through_prefix + $f_link_mapfile + $map) $link_args += ($f_link_pass_through_prefix + $f_link_start_group) + # raw_sio_pad_poll_20260802 — Task 5.1c surgical library-list trim. + # The 16 removed entries (c2, card, cd, comb, ds, gs, gun, hmd, math, + # mcrd, mcx, press, sio, snd, spu, tap) had LOAD lines in the map but + # ZERO .o files pulled in — they were unused. The 5 kept libraries + # (api, c, etc, gpu, gte) are required by the C-side calls in + # hello_joypad.c (reset_graph, draw_sync, vsync, etc.). $libraries = @( "api", "c", - "c2", - "card", - "cd", - "comb", - "ds", "etc", "gpu", - "gs", - "gte", - "gun", - "hmd", - "math", - "mcrd", - "mcx", - "pad", - "press", - "sio", - "snd", - "spu", - "tap" + "gte" ) foreach ($lib in $libraries) { $link_args += ($f_link_lib + $lib)