feat: add inital vhdl

dunkirk.sh f17b4db2 066c355d

verified
+946
+362
physics_engine.vhd
··· 1 + -- ======================================================================== 2 + -- Physics Engine 3 + -- Runs once per frame on vert_sync rising edge (~60Hz). 4 + -- Handles gravity, keyboard input, friction, bouncing off walls/floor/ 5 + -- ceiling/obstacles, and squish animation. 6 + -- 7 + -- Outputs character position and animated dimensions for the renderer. 8 + -- All velocity math is 10-bit 2's complement (bit 9 = sign). 9 + -- ======================================================================== 10 + 11 + library IEEE; 12 + use IEEE.STD_LOGIC_1164.all; 13 + use IEEE.STD_LOGIC_ARITH.all; 14 + use IEEE.STD_LOGIC_UNSIGNED.all; 15 + 16 + entity physics_engine is 17 + port( 18 + vert_sync : in std_logic; -- frame clock (~60Hz) 19 + key_w : in std_logic; -- from ps2_decoder 20 + key_a : in std_logic; 21 + key_s : in std_logic; 22 + key_d : in std_logic; 23 + char_x : out std_logic_vector(9 downto 0); -- character center X 24 + char_y : out std_logic_vector(9 downto 0); -- character center Y 25 + char_width : out std_logic_vector(9 downto 0); -- animated half-width 26 + char_height : out std_logic_vector(9 downto 0) -- animated half-height 27 + ); 28 + end physics_engine; 29 + 30 + architecture behavior of physics_engine is 31 + 32 + -- Character state 33 + signal pos_x : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(100, 10); 34 + signal pos_y : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(200, 10); 35 + signal vel_x : std_logic_vector(9 downto 0) := (others => '0'); 36 + signal vel_y : std_logic_vector(9 downto 0) := (others => '0'); 37 + 38 + constant SIZE : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(7, 10); 39 + 40 + -- Tuning constants 41 + constant GRAVITY : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(1, 10); 42 + constant IMPULSE : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(3, 10); 43 + constant JUMP_FORCE : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(13, 10); 44 + constant MAX_VEL_X : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(32, 10); 45 + 46 + -- Screen bounds 47 + constant GROUND : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(440, 10); 48 + constant CEILING : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(16, 10); 49 + constant LEFT_WALL : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(8, 10); 50 + constant RIGHT_WALL: std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(631, 10); 51 + 52 + -- Obstacle positions (must match renderer) 53 + constant O1_L : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(60, 10); 54 + constant O1_T : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(370, 10); 55 + constant O1_R : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(180, 10); 56 + constant O1_B : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(386, 10); 57 + 58 + constant O2_L : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(250, 10); 59 + constant O2_T : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(300, 10); 60 + constant O2_R : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(390, 10); 61 + constant O2_B : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(316, 10); 62 + 63 + constant O3_L : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(440, 10); 64 + constant O3_T : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(200, 10); 65 + constant O3_R : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(580, 10); 66 + constant O3_B : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(216, 10); 67 + 68 + constant O4_L : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(140, 10); 69 + constant O4_T : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(120, 10); 70 + constant O4_R : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(200, 10); 71 + constant O4_B : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(150, 10); 72 + 73 + -- Animation state 74 + signal squish : std_logic_vector(3 downto 0) := (others => '0'); 75 + signal squish_h : std_logic := '0'; -- '0'=vertical squish, '1'=horizontal 76 + signal on_ground : std_logic := '0'; 77 + signal jump_pressed : std_logic := '0'; 78 + 79 + begin 80 + 81 + -- Output character position 82 + char_x <= pos_x; 83 + char_y <= pos_y; 84 + 85 + -- Squish deforms the character: vertical hit = wider+shorter, wall hit = taller+narrower 86 + char_width <= SIZE + ("000000" & squish) when squish_h = '0' 87 + else SIZE - ("000000" & squish(3 downto 1)); 88 + char_height <= SIZE - ("000000" & squish(3 downto 1)) when squish_h = '0' 89 + else SIZE + ("000000" & squish); 90 + 91 + -- Main physics process: one tick per frame 92 + physics : process 93 + variable vx, vy : std_logic_vector(9 downto 0); 94 + variable px, py : std_logic_vector(9 downto 0); 95 + variable bounced : std_logic; 96 + variable bounce_wall : std_logic; 97 + variable bounce_speed : std_logic_vector(9 downto 0); 98 + variable grounded : std_logic; 99 + variable c_left, c_right, c_top, c_bot : std_logic_vector(9 downto 0); 100 + variable overlap_x, overlap_y : std_logic_vector(9 downto 0); 101 + begin 102 + wait until vert_sync'event and vert_sync = '1'; 103 + 104 + vx := vel_x; 105 + vy := vel_y; 106 + bounced := '0'; 107 + bounce_wall := '0'; 108 + bounce_speed := (others => '0'); 109 + grounded := '0'; 110 + 111 + -- == INPUT == 112 + 113 + if key_w = '0' then 114 + jump_pressed <= '0'; 115 + end if; 116 + 117 + -- Jump on ground (single impulse) 118 + if key_w = '1' and jump_pressed = '0' and on_ground = '1' then 119 + vy := (others => '0'); 120 + vy := vy - JUMP_FORCE; 121 + jump_pressed <= '1'; 122 + end if; 123 + 124 + -- Bounce boost: holding W adds energy each ground contact 125 + if key_w = '1' and jump_pressed = '1' and on_ground = '1' then 126 + vy := vy - 4; 127 + end if; 128 + 129 + -- Slam down (air only) 130 + if key_s = '1' and on_ground = '0' then 131 + vy := vy + IMPULSE; 132 + end if; 133 + 134 + -- Horizontal: full on ground, reduced in air 135 + if key_a = '1' then 136 + if on_ground = '1' then vx := vx - IMPULSE; 137 + else vx := vx - 2; end if; 138 + end if; 139 + if key_d = '1' then 140 + if on_ground = '1' then vx := vx + IMPULSE; 141 + else vx := vx + 2; end if; 142 + end if; 143 + 144 + -- == GRAVITY == 145 + vy := vy + GRAVITY; 146 + 147 + -- == FRICTION: vel -= vel/4, min 1 == 148 + if vx(9) = '0' then 149 + if vx > 0 then 150 + if vx(9 downto 2) = "00000000" then vx := vx - 1; 151 + else vx := vx - ("000" & vx(9 downto 3)); end if; 152 + end if; 153 + else 154 + if vx /= "0000000000" then 155 + if vx(9 downto 2) = "11111111" then vx := vx + 1; 156 + else vx := vx - ("11" & vx(9 downto 2)); end if; 157 + end if; 158 + end if; 159 + 160 + -- == CLAMP == 161 + if vx(9) = '0' and vx > MAX_VEL_X then vx := MAX_VEL_X; end if; 162 + if vx(9) = '1' and vx < (not MAX_VEL_X) + 1 then vx := (not MAX_VEL_X) + 1; end if; 163 + if vy(9) = '0' and vy > 63 then vy := CONV_STD_LOGIC_VECTOR(63, 10); end if; 164 + if vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(960, 10) then vy := CONV_STD_LOGIC_VECTOR(960, 10); end if; 165 + 166 + -- == MOVE == 167 + px := pos_x + vx; 168 + py := pos_y + vy; 169 + 170 + -- == GROUND == 171 + if py >= GROUND then 172 + py := GROUND; 173 + bounced := '1'; grounded := '1'; 174 + bounce_speed := vy; 175 + vy := (not vy) + 1; 176 + if vy(9) = '1' then 177 + vy := vy + ("000" & ((not vy(9 downto 3)) + 1)); 178 + end if; 179 + if vy(9) = '1' and vy >= CONV_STD_LOGIC_VECTOR(1022, 10) then vy := (others => '0'); 180 + elsif vy(9) = '0' then vy := (others => '0'); end if; 181 + end if; 182 + 183 + -- == CEILING == 184 + if py(9) = '1' or py <= CEILING then 185 + py := CEILING; 186 + bounced := '1'; 187 + bounce_speed := (not vy) + 1; 188 + vy := (not vy) + 1; 189 + if vy(9) = '0' and vy > 1 then 190 + vy := vy - ("000" & vy(9 downto 3)); 191 + end if; 192 + end if; 193 + 194 + -- == LEFT WALL == 195 + if px(9) = '1' or px <= LEFT_WALL then 196 + px := LEFT_WALL; 197 + bounced := '1'; bounce_wall := '1'; 198 + bounce_speed := (not vx) + 1; 199 + vx := (not vx) + 1; 200 + if vx(9) = '0' and vx > 1 then 201 + vx := vx - ("000" & vx(9 downto 3)); 202 + end if; 203 + end if; 204 + 205 + -- == RIGHT WALL == 206 + if px >= RIGHT_WALL then 207 + px := RIGHT_WALL; 208 + bounced := '1'; bounce_wall := '1'; 209 + bounce_speed := vx; 210 + vx := (not vx) + 1; 211 + -- vx is now negative: reduce magnitude toward zero 212 + if vx(9) = '1' then 213 + vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); 214 + end if; 215 + -- Kill tiny bounces 216 + if vx(9) = '1' and vx >= CONV_STD_LOGIC_VECTOR(1022, 10) then vx := (others => '0'); end if; 217 + end if; 218 + 219 + -- == OBSTACLE COLLISIONS == 220 + -- Pattern: AABB overlap -> resolve on shallower axis -> bounce 221 + 222 + -- Obstacle 1 223 + c_left := px - SIZE; c_right := px + SIZE; 224 + c_top := py - SIZE; c_bot := py + SIZE; 225 + if (c_right >= O1_L) and (c_left <= O1_R) and 226 + (c_bot >= O1_T) and (c_top <= O1_B) then 227 + if vy(9) = '0' then overlap_y := c_bot - O1_T; 228 + else overlap_y := O1_B - c_top; end if; 229 + if vx(9) = '0' then overlap_x := c_right - O1_L; 230 + else overlap_x := O1_R - c_left; end if; 231 + if overlap_y <= overlap_x then 232 + if vy(9) = '0' then py := O1_T - SIZE; grounded := '1'; 233 + else py := O1_B + SIZE; end if; 234 + bounced := '1'; 235 + vy := (not vy) + 1; 236 + if vy(9) = '0' and vy > 1 then vy := vy - ("000" & vy(9 downto 3)); 237 + elsif vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(1022, 10) then 238 + vy := vy + ("000" & ((not vy(9 downto 3)) + 1)); end if; 239 + if vy(9) = '0' and vy < 2 then vy := (others => '0'); end if; 240 + if vy(9) = '1' and vy >= CONV_STD_LOGIC_VECTOR(1022, 10) then vy := (others => '0'); end if; 241 + else 242 + if vx(9) = '0' then px := O1_L - SIZE; 243 + else px := O1_R + SIZE; end if; 244 + bounced := '1'; bounce_wall := '1'; 245 + vx := (not vx) + 1; 246 + if vx(9) = '0' and vx > 1 then vx := vx - ("000" & vx(9 downto 3)); 247 + elsif vx(9) = '1' and vx < CONV_STD_LOGIC_VECTOR(1022, 10) then 248 + vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); end if; 249 + end if; 250 + end if; 251 + 252 + -- Obstacle 2 253 + c_left := px - SIZE; c_right := px + SIZE; 254 + c_top := py - SIZE; c_bot := py + SIZE; 255 + if (c_right >= O2_L) and (c_left <= O2_R) and 256 + (c_bot >= O2_T) and (c_top <= O2_B) then 257 + if vy(9) = '0' then overlap_y := c_bot - O2_T; 258 + else overlap_y := O2_B - c_top; end if; 259 + if vx(9) = '0' then overlap_x := c_right - O2_L; 260 + else overlap_x := O2_R - c_left; end if; 261 + if overlap_y <= overlap_x then 262 + if vy(9) = '0' then py := O2_T - SIZE; grounded := '1'; 263 + else py := O2_B + SIZE; end if; 264 + bounced := '1'; 265 + vy := (not vy) + 1; 266 + if vy(9) = '0' and vy > 1 then vy := vy - ("000" & vy(9 downto 3)); 267 + elsif vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(1022, 10) then 268 + vy := vy + ("000" & ((not vy(9 downto 3)) + 1)); end if; 269 + if vy(9) = '0' and vy < 2 then vy := (others => '0'); end if; 270 + if vy(9) = '1' and vy >= CONV_STD_LOGIC_VECTOR(1022, 10) then vy := (others => '0'); end if; 271 + else 272 + if vx(9) = '0' then px := O2_L - SIZE; 273 + else px := O2_R + SIZE; end if; 274 + bounced := '1'; bounce_wall := '1'; 275 + vx := (not vx) + 1; 276 + if vx(9) = '0' and vx > 1 then vx := vx - ("000" & vx(9 downto 3)); 277 + elsif vx(9) = '1' and vx < CONV_STD_LOGIC_VECTOR(1022, 10) then 278 + vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); end if; 279 + end if; 280 + end if; 281 + 282 + -- Obstacle 3 283 + c_left := px - SIZE; c_right := px + SIZE; 284 + c_top := py - SIZE; c_bot := py + SIZE; 285 + if (c_right >= O3_L) and (c_left <= O3_R) and 286 + (c_bot >= O3_T) and (c_top <= O3_B) then 287 + if vy(9) = '0' then overlap_y := c_bot - O3_T; 288 + else overlap_y := O3_B - c_top; end if; 289 + if vx(9) = '0' then overlap_x := c_right - O3_L; 290 + else overlap_x := O3_R - c_left; end if; 291 + if overlap_y <= overlap_x then 292 + if vy(9) = '0' then py := O3_T - SIZE; grounded := '1'; 293 + else py := O3_B + SIZE; end if; 294 + bounced := '1'; 295 + vy := (not vy) + 1; 296 + if vy(9) = '0' and vy > 1 then vy := vy - ("000" & vy(9 downto 3)); 297 + elsif vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(1022, 10) then 298 + vy := vy + ("000" & ((not vy(9 downto 3)) + 1)); end if; 299 + if vy(9) = '0' and vy < 2 then vy := (others => '0'); end if; 300 + if vy(9) = '1' and vy >= CONV_STD_LOGIC_VECTOR(1022, 10) then vy := (others => '0'); end if; 301 + else 302 + if vx(9) = '0' then px := O3_L - SIZE; 303 + else px := O3_R + SIZE; end if; 304 + bounced := '1'; bounce_wall := '1'; 305 + vx := (not vx) + 1; 306 + if vx(9) = '0' and vx > 1 then vx := vx - ("000" & vx(9 downto 3)); 307 + elsif vx(9) = '1' and vx < CONV_STD_LOGIC_VECTOR(1022, 10) then 308 + vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); end if; 309 + end if; 310 + end if; 311 + 312 + -- Obstacle 4 313 + c_left := px - SIZE; c_right := px + SIZE; 314 + c_top := py - SIZE; c_bot := py + SIZE; 315 + if (c_right >= O4_L) and (c_left <= O4_R) and 316 + (c_bot >= O4_T) and (c_top <= O4_B) then 317 + if vy(9) = '0' then overlap_y := c_bot - O4_T; 318 + else overlap_y := O4_B - c_top; end if; 319 + if vx(9) = '0' then overlap_x := c_right - O4_L; 320 + else overlap_x := O4_R - c_left; end if; 321 + if overlap_y <= overlap_x then 322 + if vy(9) = '0' then py := O4_T - SIZE; grounded := '1'; 323 + else py := O4_B + SIZE; end if; 324 + bounced := '1'; 325 + vy := (not vy) + 1; 326 + if vy(9) = '0' and vy > 1 then vy := vy - ("000" & vy(9 downto 3)); 327 + elsif vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(1022, 10) then 328 + vy := vy + ("000" & ((not vy(9 downto 3)) + 1)); end if; 329 + if vy(9) = '0' and vy < 2 then vy := (others => '0'); end if; 330 + if vy(9) = '1' and vy >= CONV_STD_LOGIC_VECTOR(1022, 10) then vy := (others => '0'); end if; 331 + else 332 + if vx(9) = '0' then px := O4_L - SIZE; 333 + else px := O4_R + SIZE; end if; 334 + bounced := '1'; bounce_wall := '1'; 335 + vx := (not vx) + 1; 336 + if vx(9) = '0' and vx > 1 then vx := vx - ("000" & vx(9 downto 3)); 337 + elsif vx(9) = '1' and vx < CONV_STD_LOGIC_VECTOR(1022, 10) then 338 + vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); end if; 339 + end if; 340 + end if; 341 + 342 + -- == COMMIT == 343 + vel_x <= vx; 344 + vel_y <= vy; 345 + pos_x <= px; 346 + pos_y <= py; 347 + 348 + -- Squish on meaningful impacts only 349 + if bounced = '1' and bounce_speed > 3 then 350 + if bounce_speed >= 8 then squish <= "1000"; 351 + else squish <= bounce_speed(3 downto 0); end if; 352 + squish_h <= bounce_wall; 353 + elsif squish > 0 then 354 + squish <= squish - 1; 355 + end if; 356 + 357 + if grounded = '1' then on_ground <= '1'; 358 + elsif py < GROUND - 1 then on_ground <= '0'; end if; 359 + 360 + end process physics; 361 + 362 + end behavior;
+80
ps2_decoder.vhd
··· 1 + -- ======================================================================== 2 + -- PS2 Keyboard Decoder 3 + -- Receives serial data from a PS2 keyboard and outputs which 4 + -- WASD keys are currently held down. 5 + -- 6 + -- PS2 protocol: 11 bits per key event (start, 8 data LSB first, parity, stop) 7 + -- Make code = key pressed, F0 + code = key released 8 + -- Scan codes: W=1D, A=1C, S=1B, D=23 9 + -- ======================================================================== 10 + 11 + library IEEE; 12 + use IEEE.STD_LOGIC_1164.all; 13 + use IEEE.STD_LOGIC_UNSIGNED.all; 14 + 15 + entity ps2_decoder is 16 + port( 17 + ps2_clk : in std_logic; -- clock from keyboard 18 + ps2_data : in std_logic; -- serial data from keyboard 19 + key_w : out std_logic; -- '1' when W is held 20 + key_a : out std_logic; -- '1' when A is held 21 + key_s : out std_logic; -- '1' when S is held 22 + key_d : out std_logic -- '1' when D is held 23 + ); 24 + end ps2_decoder; 25 + 26 + architecture behavior of ps2_decoder is 27 + signal shift_reg : std_logic_vector(10 downto 0); 28 + signal bit_count : std_logic_vector(3 downto 0) := (others => '0'); 29 + signal break_flag : std_logic := '0'; 30 + signal scan_code : std_logic_vector(7 downto 0); 31 + begin 32 + 33 + -- Single process: shift in bits, decode when frame complete 34 + ps2_receive : process(ps2_clk) 35 + begin 36 + if ps2_clk'event and ps2_clk = '0' then 37 + -- Shift in new bit (MSB first into shift register) 38 + shift_reg <= ps2_data & shift_reg(10 downto 1); 39 + 40 + if bit_count = "1010" then 41 + -- 11th bit received (stop bit), frame complete 42 + -- Data byte is in shift_reg(9 downto 2) at this point: 43 + -- shift_reg(10) = parity (from previous edge) 44 + -- shift_reg(9) = data bit 7 45 + -- shift_reg(2) = data bit 0 46 + -- shift_reg(1) = start bit 47 + scan_code <= shift_reg(9 downto 2); 48 + 49 + -- Decode: F0 means next byte is a key release 50 + if shift_reg(9 downto 2) = X"F0" then 51 + break_flag <= '1'; 52 + elsif break_flag = '1' then 53 + -- Key released 54 + break_flag <= '0'; 55 + case shift_reg(9 downto 2) is 56 + when X"1D" => key_w <= '0'; 57 + when X"1C" => key_a <= '0'; 58 + when X"1B" => key_s <= '0'; 59 + when X"23" => key_d <= '0'; 60 + when others => null; 61 + end case; 62 + else 63 + -- Key pressed 64 + case shift_reg(9 downto 2) is 65 + when X"1D" => key_w <= '1'; 66 + when X"1C" => key_a <= '1'; 67 + when X"1B" => key_s <= '1'; 68 + when X"23" => key_d <= '1'; 69 + when others => null; 70 + end case; 71 + end if; 72 + 73 + bit_count <= (others => '0'); 74 + else 75 + bit_count <= bit_count + 1; 76 + end if; 77 + end if; 78 + end process ps2_receive; 79 + 80 + end behavior;
+120
renderer.vhd
··· 1 + -- ======================================================================== 2 + -- Renderer 3 + -- Combinational logic: given the current pixel position and character 4 + -- state, outputs the RGB color for that pixel. 5 + -- 6 + -- Draws: character (red), 4 obstacles (yellow), ground/ceiling (green), 7 + -- walls (cyan), sky (black) 8 + -- ======================================================================== 9 + 10 + library IEEE; 11 + use IEEE.STD_LOGIC_1164.all; 12 + use IEEE.STD_LOGIC_ARITH.all; 13 + use IEEE.STD_LOGIC_UNSIGNED.all; 14 + 15 + entity renderer is 16 + port( 17 + pixel_row : in std_logic_vector(9 downto 0); -- from vga_sync 18 + pixel_column : in std_logic_vector(9 downto 0); -- from vga_sync 19 + char_x : in std_logic_vector(9 downto 0); -- from physics 20 + char_y : in std_logic_vector(9 downto 0); -- from physics 21 + char_width : in std_logic_vector(9 downto 0); -- from physics (animated) 22 + char_height : in std_logic_vector(9 downto 0); -- from physics (animated) 23 + red : out std_logic; 24 + green : out std_logic; 25 + blue : out std_logic 26 + ); 27 + end renderer; 28 + 29 + architecture behavior of renderer is 30 + 31 + -- Screen boundaries 32 + constant GROUND_TOP : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(448, 10); 33 + constant CEIL_BOT : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(8, 10); 34 + constant LEFT_WALL : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(8, 10); 35 + constant RIGHT_WALL : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(631, 10); 36 + 37 + -- Obstacle positions (must match physics_engine) 38 + constant O1_L : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(60, 10); 39 + constant O1_T : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(370, 10); 40 + constant O1_R : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(180, 10); 41 + constant O1_B : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(386, 10); 42 + 43 + constant O2_L : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(250, 10); 44 + constant O2_T : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(300, 10); 45 + constant O2_R : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(390, 10); 46 + constant O2_B : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(316, 10); 47 + 48 + constant O3_L : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(440, 10); 49 + constant O3_T : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(200, 10); 50 + constant O3_R : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(580, 10); 51 + constant O3_B : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(216, 10); 52 + 53 + constant O4_L : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(140, 10); 54 + constant O4_T : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(120, 10); 55 + constant O4_R : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(200, 10); 56 + constant O4_B : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(150, 10); 57 + 58 + signal char_on, ground_on, wall_on, ceiling_on, obs_on : std_logic; 59 + 60 + begin 61 + 62 + -- Check what the current pixel overlaps 63 + render : process(pixel_row, pixel_column, char_x, char_y, 64 + char_width, char_height) 65 + begin 66 + char_on <= '0'; 67 + ground_on <= '0'; 68 + wall_on <= '0'; 69 + ceiling_on <= '0'; 70 + obs_on <= '0'; 71 + 72 + -- Character 73 + if (pixel_column >= char_x - char_width) and 74 + (pixel_column <= char_x + char_width) and 75 + (pixel_row >= char_y - char_height) and 76 + (pixel_row <= char_y + char_height) then 77 + char_on <= '1'; 78 + end if; 79 + 80 + -- Ground 81 + if pixel_row >= GROUND_TOP then ground_on <= '1'; end if; 82 + 83 + -- Ceiling 84 + if pixel_row <= CEIL_BOT then ceiling_on <= '1'; end if; 85 + 86 + -- Walls 87 + if (pixel_column < LEFT_WALL) or (pixel_column > RIGHT_WALL) then 88 + wall_on <= '1'; 89 + end if; 90 + 91 + -- Obstacles 92 + if (pixel_column >= O1_L) and (pixel_column <= O1_R) and 93 + (pixel_row >= O1_T) and (pixel_row <= O1_B) then obs_on <= '1'; end if; 94 + if (pixel_column >= O2_L) and (pixel_column <= O2_R) and 95 + (pixel_row >= O2_T) and (pixel_row <= O2_B) then obs_on <= '1'; end if; 96 + if (pixel_column >= O3_L) and (pixel_column <= O3_R) and 97 + (pixel_row >= O3_T) and (pixel_row <= O3_B) then obs_on <= '1'; end if; 98 + if (pixel_column >= O4_L) and (pixel_column <= O4_R) and 99 + (pixel_row >= O4_T) and (pixel_row <= O4_B) then obs_on <= '1'; end if; 100 + end process render; 101 + 102 + -- Priority color encoder 103 + -- Character=Red(100), Obstacles=Yellow(110), Ground/Ceil=Green(010), 104 + -- Walls=Green(010), Sky=Black(000) 105 + color : process(char_on, ground_on, wall_on, ceiling_on, obs_on) 106 + begin 107 + if char_on = '1' then 108 + red <= '1'; green <= '0'; blue <= '0'; 109 + elsif obs_on = '1' then 110 + red <= '1'; green <= '1'; blue <= '0'; 111 + elsif ground_on = '1' or ceiling_on = '1' then 112 + red <= '0'; green <= '1'; blue <= '0'; 113 + elsif wall_on = '1' then 114 + red <= '0'; green <= '1'; blue <= '0'; 115 + else 116 + red <= '0'; green <= '0'; blue <= '0'; 117 + end if; 118 + end process color; 119 + 120 + end behavior;
+384
visualizer.py
··· 1 + #!/usr/bin/env python3 2 + """ 3 + Bouncy Game Visualizer — mirrors the VHDL physics exactly. 4 + 5 + Controls: WASD to move/jump, R to reset, T to toggle trail, Q to quit. 6 + 7 + Prerequisites: 8 + pip install pygame 9 + 10 + Usage: 11 + python3 visualizer.py 12 + """ 13 + 14 + import pygame 15 + import sys 16 + 17 + # --- Screen / VGA constants (match VHDL) --- 18 + SCREEN_W = 640 19 + SCREEN_H = 480 20 + FPS = 60 21 + 22 + # --- Physics constants (match VHDL exactly) --- 23 + GRAVITY = 1 24 + IMPULSE = 3 25 + AIR_CONTROL = 2 26 + JUMP_FORCE = 13 27 + MAX_VEL_X = 32 28 + SIZE = 7 29 + BOUNCE_SHIFT = 3 # energy loss = vel >> 3 (keep 87.5%) 30 + 31 + # --- Bounds (match VHDL) --- 32 + GROUND = 440 33 + CEILING = 16 34 + LEFT_WALL = 8 35 + RIGHT_WALL = 620 36 + GROUND_TOP = 448 37 + CEIL_BOT = 8 38 + 39 + # --- Obstacles: (left, top, right, bottom) matching VHDL --- 40 + OBSTACLES = [ 41 + (60, 370, 180, 386), # Low platform left 42 + (250, 300, 390, 316), # Middle floating platform 43 + (440, 200, 580, 216), # High platform right 44 + (140, 120, 200, 150), # Small block upper-left 45 + ] 46 + 47 + # --- Colors (1-bit RGB) --- 48 + COLOR_SKY = (0, 0, 0) 49 + COLOR_GROUND = (0, 255, 0) 50 + COLOR_CEIL = (0, 255, 0) 51 + COLOR_WALL = (0, 255, 255) 52 + COLOR_CHAR = (255, 0, 0) 53 + COLOR_OBS = (255, 255, 0) 54 + 55 + # --- 10-bit signed helpers --- 56 + MASK = 0x3FF 57 + 58 + def to_signed(val): 59 + val = val & MASK 60 + return val - 1024 if val >= 512 else val 61 + 62 + def to_unsigned(val): 63 + return val & MASK 64 + 65 + def negate(v): 66 + return ((~v) + 1) & MASK 67 + 68 + 69 + def main(): 70 + pygame.init() 71 + screen = pygame.display.set_mode((SCREEN_W, SCREEN_H)) 72 + pygame.display.set_caption("Bouncy Game — VHDL Physics Preview") 73 + clock = pygame.time.Clock() 74 + 75 + char_x = 100 76 + char_y = 200 77 + vel_x = to_unsigned(0) 78 + vel_y = to_unsigned(0) 79 + squish = 0 80 + squish_h = False # False = vertical squish (floor/ceil), True = horizontal squish (walls) 81 + on_ground = False 82 + jump_pressed = False 83 + 84 + keys_held = {'w': False, 'a': False, 's': False, 'd': False} 85 + trail = [] 86 + show_trail = True 87 + font = pygame.font.SysFont("monospace", 14) 88 + 89 + running = True 90 + while running: 91 + for event in pygame.event.get(): 92 + if event.type == pygame.QUIT: 93 + running = False 94 + elif event.type == pygame.KEYDOWN: 95 + if event.key == pygame.K_w: keys_held['w'] = True 96 + elif event.key == pygame.K_a: keys_held['a'] = True 97 + elif event.key == pygame.K_s: keys_held['s'] = True 98 + elif event.key == pygame.K_d: keys_held['d'] = True 99 + elif event.key == pygame.K_q: running = False 100 + elif event.key == pygame.K_t: 101 + show_trail = not show_trail; trail.clear() 102 + elif event.key == pygame.K_r: 103 + char_x, char_y = 100, 200 104 + vel_x = vel_y = to_unsigned(0) 105 + squish = 0; squish_h = False; on_ground = False; jump_pressed = False 106 + trail.clear() 107 + elif event.type == pygame.KEYUP: 108 + if event.key == pygame.K_w: keys_held['w'] = False 109 + elif event.key == pygame.K_a: keys_held['a'] = False 110 + elif event.key == pygame.K_s: keys_held['s'] = False 111 + elif event.key == pygame.K_d: keys_held['d'] = False 112 + 113 + # ============================================================= 114 + # Physics — matches VHDL exactly 115 + # ============================================================= 116 + vx = vel_x 117 + vy = vel_y 118 + bounced = False 119 + bounce_wall = False 120 + bounce_speed = 0 121 + grounded = False 122 + 123 + # Jump latch 124 + if not keys_held['w']: 125 + jump_pressed = False 126 + 127 + # First press on ground: full jump 128 + if keys_held['w'] and not jump_pressed and on_ground: 129 + vy = to_unsigned(0) 130 + vy = (vy - JUMP_FORCE) & MASK 131 + jump_pressed = True 132 + 133 + # Holding W while bouncing: boost each ground contact 134 + if keys_held['w'] and jump_pressed and on_ground: 135 + vy = (vy - 4) & MASK 136 + 137 + # S: slam (air only) 138 + if keys_held['s'] and not on_ground: 139 + vy = (vy + IMPULSE) & MASK 140 + 141 + # A/D 142 + if keys_held['a']: 143 + if on_ground: 144 + vx = (vx - IMPULSE) & MASK 145 + else: 146 + vx = (vx - AIR_CONTROL) & MASK 147 + 148 + if keys_held['d']: 149 + if on_ground: 150 + vx = (vx + IMPULSE) & MASK 151 + else: 152 + vx = (vx + AIR_CONTROL) & MASK 153 + 154 + # Gravity 155 + vy = (vy + GRAVITY) & MASK 156 + 157 + # Friction: vel -= vel/4, min 1 158 + svx = to_signed(vx) 159 + if svx > 0: 160 + drag = svx >> 2 161 + if drag == 0: drag = 1 162 + svx -= drag 163 + elif svx < 0: 164 + drag = (-svx) >> 2 165 + if drag == 0: drag = 1 166 + svx += drag 167 + vx = to_unsigned(svx) 168 + 169 + # Clamp X 170 + svx = to_signed(vx) 171 + if svx > MAX_VEL_X: svx = MAX_VEL_X 172 + elif svx < -MAX_VEL_X: svx = -MAX_VEL_X 173 + vx = to_unsigned(svx) 174 + 175 + # Clamp Y 176 + svy = to_signed(vy) 177 + if svy > 63: svy = 63 178 + elif svy < -64: svy = -64 179 + vy = to_unsigned(svy) 180 + 181 + # Update position 182 + px = char_x + to_signed(vx) 183 + py = char_y + to_signed(vy) 184 + 185 + # --- Ground bounce --- 186 + if py >= GROUND: 187 + py = GROUND 188 + bounced = True 189 + grounded = True 190 + bounce_speed = abs(to_signed(vy)) 191 + vy = negate(vy) 192 + svy = to_signed(vy) 193 + # svy is now negative (upward), apply energy loss on magnitude 194 + if svy < -1: 195 + svy += (-svy) >> BOUNCE_SHIFT # reduce magnitude 196 + # Kill tiny bounces 197 + if abs(svy) < 2: 198 + svy = 0 199 + vy = to_unsigned(svy) 200 + 201 + # --- Ceiling bounce --- 202 + if py <= CEILING: 203 + py = CEILING 204 + bounced = True 205 + bounce_speed = abs(to_signed(vy)) 206 + vy = negate(vy) 207 + svy = to_signed(vy) 208 + if abs(svy) > 1: 209 + svy_abs = abs(svy) 210 + loss = svy_abs >> BOUNCE_SHIFT 211 + if svy > 0: 212 + svy -= loss 213 + else: 214 + svy += loss 215 + vy = to_unsigned(svy) 216 + 217 + # --- Left wall bounce --- 218 + if px <= LEFT_WALL: 219 + px = LEFT_WALL 220 + bounced = True; bounce_wall = True 221 + bounce_speed = abs(to_signed(vx)) 222 + vx = negate(vx) 223 + svx = to_signed(vx) 224 + if svx > 1: 225 + svx -= svx >> BOUNCE_SHIFT 226 + vx = to_unsigned(svx) 227 + 228 + # --- Right wall bounce --- 229 + if px >= RIGHT_WALL: 230 + px = RIGHT_WALL 231 + bounced = True; bounce_wall = True 232 + bounce_speed = abs(to_signed(vx)) 233 + vx = negate(vx) 234 + svx = to_signed(vx) 235 + if svx < -1: 236 + svx += (-svx) >> BOUNCE_SHIFT 237 + vx = to_unsigned(svx) 238 + 239 + # --- Obstacle collisions --- 240 + for (ol, ot, orr, ob) in OBSTACLES: 241 + c_left = px - SIZE 242 + c_right = px + SIZE 243 + c_top = py - SIZE 244 + c_bot = py + SIZE 245 + 246 + if c_right >= ol and c_left <= orr and c_bot >= ot and c_top <= ob: 247 + # Compute overlap from velocity direction 248 + svx_now = to_signed(vx) 249 + svy_now = to_signed(vy) 250 + 251 + if svx_now >= 0: 252 + overlap_x = c_right - ol 253 + else: 254 + overlap_x = orr - c_left 255 + 256 + if svy_now >= 0: 257 + overlap_y = c_bot - ot 258 + else: 259 + overlap_y = ob - c_top 260 + 261 + if overlap_y <= overlap_x: 262 + # Vertical resolution 263 + if svy_now >= 0: # moving down 264 + py = ot - SIZE 265 + grounded = True 266 + else: # moving up 267 + py = ob + SIZE 268 + bounced = True 269 + bounce_speed = abs(svy_now) 270 + vy = negate(vy) 271 + svy = to_signed(vy) 272 + # Energy loss on magnitude 273 + if abs(svy) > 1: 274 + svy_abs = abs(svy) 275 + loss = svy_abs >> BOUNCE_SHIFT 276 + if svy > 0: 277 + svy -= loss 278 + else: 279 + svy += loss 280 + if abs(svy) < 2: 281 + svy = 0 282 + vy = to_unsigned(svy) 283 + else: 284 + # Horizontal resolution 285 + if svx_now >= 0: 286 + px = ol - SIZE 287 + else: 288 + px = orr + SIZE 289 + bounced = True; bounce_wall = True 290 + bounce_speed = abs(svx_now) 291 + vx = negate(vx) 292 + svx = to_signed(vx) 293 + if svx > 1: 294 + svx -= svx >> BOUNCE_SHIFT 295 + elif svx < -1: 296 + svx += (-svx) >> BOUNCE_SHIFT 297 + vx = to_unsigned(svx) 298 + 299 + # Commit 300 + char_x = px 301 + char_y = py 302 + vel_x = vx 303 + vel_y = vy 304 + 305 + # On ground 306 + on_ground = grounded or (py >= GROUND - 1) 307 + 308 + # Squish — only on impacts with real velocity, not idle ground contact 309 + if bounced and bounce_speed > 3: 310 + squish = min(bounce_speed, 8) 311 + squish_h = bounce_wall 312 + elif squish > 0: 313 + squish -= 1 314 + 315 + # Trail 316 + if show_trail: 317 + trail.append((char_x, char_y)) 318 + if len(trail) > 300: 319 + trail.pop(0) 320 + 321 + # ============================================================= 322 + # Rendering 323 + # ============================================================= 324 + screen.fill(COLOR_SKY) 325 + 326 + # Ceiling 327 + pygame.draw.rect(screen, COLOR_CEIL, (0, 0, SCREEN_W, CEIL_BOT)) 328 + 329 + # Ground 330 + pygame.draw.rect(screen, COLOR_GROUND, 331 + (0, GROUND_TOP, SCREEN_W, SCREEN_H - GROUND_TOP)) 332 + 333 + # Walls 334 + pygame.draw.rect(screen, COLOR_WALL, (0, 0, LEFT_WALL, SCREEN_H)) 335 + pygame.draw.rect(screen, COLOR_WALL, 336 + (RIGHT_WALL, 0, SCREEN_W - RIGHT_WALL, SCREEN_H)) 337 + 338 + # Obstacles 339 + for (ol, ot, orr, ob) in OBSTACLES: 340 + pygame.draw.rect(screen, COLOR_OBS, 341 + (ol, ot, orr - ol, ob - ot)) 342 + 343 + # Trail 344 + if show_trail and len(trail) > 1: 345 + for i, (tx, ty) in enumerate(trail): 346 + alpha = int(80 * i / len(trail)) 347 + s = pygame.Surface((3, 3)) 348 + s.set_alpha(alpha) 349 + s.fill((255, 100, 100)) 350 + screen.blit(s, (tx - 1, ty - 1)) 351 + 352 + # Character with squish 353 + # Vertical squish (floor/ceil): wider + shorter 354 + # Horizontal squish (walls): taller + narrower 355 + if not squish_h: 356 + cw = SIZE + squish 357 + ch = SIZE - squish // 2 358 + else: 359 + cw = SIZE - squish // 2 360 + ch = SIZE + squish 361 + pygame.draw.rect(screen, COLOR_CHAR, 362 + (char_x - cw, char_y - ch, cw * 2, ch * 2)) 363 + 364 + # HUD 365 + svx = to_signed(vel_x) 366 + svy = to_signed(vel_y) 367 + info = [ 368 + f"pos: ({char_x}, {char_y}) vel: ({svx}, {svy})", 369 + f"squish: {squish} ground: {'yes' if on_ground else 'no'}", 370 + "", 371 + "WASD: move/jump R: reset T: trail Q: quit", 372 + ] 373 + for i, line in enumerate(info): 374 + surf = font.render(line, True, (255, 255, 255)) 375 + screen.blit(surf, (LEFT_WALL + 4, CEIL_BOT + 4 + i * 16)) 376 + 377 + pygame.display.flip() 378 + clock.tick(FPS) 379 + 380 + pygame.quit() 381 + 382 + 383 + if __name__ == "__main__": 384 + main()