at main 466 lines 20 kB view raw
1-- ======================================================================== 2-- Physics Engine (scrolling 1500x1500 world) 3-- Positions: 11-bit unsigned. Valid range: 0..1492 (fits in 11-bit fine). 4-- Velocities: 10-bit 2's complement, bit 9 = sign. 5-- 6-- Underflow detection: uses >= 2000 threshold instead of bit-10 sign check. 7-- Bit-10 was wrong because GROUND(1480) and RIGHT_WALL(1492) both have 8-- bit 10 set (values >= 1024), causing false ceiling/wall triggers. 9-- 10-- Obstacles imported from level package (use work.level.all). 11-- ======================================================================== 12 13library IEEE; 14use IEEE.STD_LOGIC_1164.all; 15use IEEE.STD_LOGIC_ARITH.all; 16use IEEE.STD_LOGIC_UNSIGNED.all; 17use work.level.all; 18 19entity physics_engine is 20 port( 21 vert_sync : in std_logic; 22 key_w : in std_logic; 23 key_a : in std_logic; 24 key_s : in std_logic; 25 key_d : in std_logic; 26 char_x : out std_logic_vector(10 downto 0); 27 char_y : out std_logic_vector(10 downto 0); 28 char_width : out std_logic_vector(9 downto 0); 29 char_height : out std_logic_vector(9 downto 0); 30 cam_x : out std_logic_vector(10 downto 0); 31 cam_y : out std_logic_vector(10 downto 0); 32 vel_out : out std_logic_vector(9 downto 0); 33 vel_x_out : out std_logic_vector(9 downto 0); 34 vel_y_out : out std_logic_vector(9 downto 0) 35 ); 36end physics_engine; 37 38architecture behavior of physics_engine is 39 40 -- Character state 41 signal pos_x : std_logic_vector(10 downto 0) := CONV_STD_LOGIC_VECTOR(100, 11); 42 signal pos_y : std_logic_vector(10 downto 0) := CONV_STD_LOGIC_VECTOR(1400, 11); 43 signal vel_x : std_logic_vector(9 downto 0) := (others => '0'); 44 signal vel_y : std_logic_vector(9 downto 0) := (others => '0'); 45 signal cam_x_sig : std_logic_vector(10 downto 0) := (others => '0'); 46 signal cam_y_sig : std_logic_vector(10 downto 0) := CONV_STD_LOGIC_VECTOR(1020, 11); 47 48 -- SIZE: 10-bit for squish output math, 11-bit for position arithmetic 49 constant SIZE : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(7, 10); 50 constant SIZE11 : std_logic_vector(10 downto 0) := CONV_STD_LOGIC_VECTOR(7, 11); 51 52 -- Physics tuning 53 constant GRAVITY : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(1, 10); 54 constant IMPULSE : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(3, 10); 55 constant SLAM_TAP_FORCE : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(18, 10); 56 constant SLAM_BOOST_CLOSE : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(24, 10); 57 constant SLAM_BOOST_MED : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(16, 10); 58 constant SLAM_BOOST_FAR : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(8, 10); 59 constant SLAM_CLOSE_THR : std_logic_vector(10 downto 0) := CONV_STD_LOGIC_VECTOR(270, 11); 60 constant SLAM_MED_THR : std_logic_vector(10 downto 0) := CONV_STD_LOGIC_VECTOR(660, 11); 61 -- Initial jump: full force at zero velocity, tapers off as |vel_y| rises. 62 -- Piecewise lookup approximates: max(JUMP_MIN, JUMP_BASE*JUMP_SCALE/(JUMP_SCALE+|vy|)) 63 constant JUMP_BASE : integer := 14; -- force at zero velocity 64 constant JUMP_VEL_THR1 : integer := 3; -- |vy| <= 3 → force 14 65 constant JUMP_VEL_THR2 : integer := 10; -- |vy| <= 10 → force 8 66 constant JUMP_VEL_THR3 : integer := 20; -- |vy| <= 20 → force 6 67 constant JUMP_MIN_FORCE : integer := 4; -- floor at high velocity 68 -- Trampoline: fixed additive boost on each ground contact while W held. 69 -- No inverse scaling so energy accumulates each bounce. 70 constant JUMP_BOUNCE : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(6, 10); 71 constant MAX_VEL_X : std_logic_vector(9 downto 0) := CONV_STD_LOGIC_VECTOR(32, 10); 72 73 -- World bounds (11-bit) 74 constant GROUND : std_logic_vector(10 downto 0) := CONV_STD_LOGIC_VECTOR(1480, 11); 75 constant CEILING : std_logic_vector(10 downto 0) := CONV_STD_LOGIC_VECTOR(16, 11); 76 constant LEFT_WALL : std_logic_vector(10 downto 0) := CONV_STD_LOGIC_VECTOR(8, 11); 77 constant RIGHT_WALL : std_logic_vector(10 downto 0) := CONV_STD_LOGIC_VECTOR(1492, 11); 78 79 -- Underflow sentinel: valid positions top out at 1480 (GROUND). 80 -- CEILING(16) - max_upward(128) = -112 -> 11-bit unsigned = 1936. 81 -- So UNDERFLOW must be <= 1936 and > 1480. 1500 gives safe margin. 82 constant UNDERFLOW : std_logic_vector(10 downto 0) := CONV_STD_LOGIC_VECTOR(1500, 11); 83 84 -- Tune: vertical speed (0-63) at which all 10 LEDs are fully lit. 85 -- Lower = more sensitive (fewer LEDs at low speed fill up faster). 86 -- Higher = better dynamic range across the full velocity range. 87 -- Higher = less sensitive (need a harder bounce to light all LEDs). 88 constant LED_FULL_VEL : integer := 48; 89 90 -- Animation 91 signal squish : std_logic_vector(3 downto 0) := (others => '0'); 92 signal squish_h : std_logic := '0'; 93 signal on_ground : std_logic := '0'; 94 signal jump_pressed : std_logic := '0'; 95 signal slam_held : std_logic := '0'; 96 signal slam_tap : std_logic := '0'; -- S pressed then released before landing 97 signal slam_start_y : std_logic_vector(10 downto 0) := (others => '0'); 98 signal prev_key_s : std_logic := '0'; 99 100 signal abs_vel_x : std_logic_vector(9 downto 0); 101 signal abs_vel_y : std_logic_vector(9 downto 0); 102 103begin 104 105 char_x <= pos_x; 106 char_y <= pos_y; 107 cam_x <= cam_x_sig; 108 cam_y <= cam_y_sig; 109 vel_x_out <= vel_x; 110 vel_y_out <= vel_y; 111 112 -- Absolute values of both velocity axes. 113 abs_vel_x <= (not vel_x) + 1 when vel_x(9) = '1' else vel_x; 114 abs_vel_y <= (not vel_y) + 1 when vel_y(9) = '1' else vel_y; 115 116 -- Thermometer-encode peak speed (max of |vx|,|vy|) → horizontal LED bar. 117 -- n LEDs lit from LSB up → 0000011111 for n=5, 1111111111 for n=10. 118 vel_bar : process(abs_vel_x, abs_vel_y) 119 variable vx, vy, vmax : integer; 120 variable n : integer range 0 to 10; 121 begin 122 vx := CONV_INTEGER(abs_vel_x); 123 vy := CONV_INTEGER(abs_vel_y); 124 if vx > vy then vmax := vx; else vmax := vy; end if; 125 if vmax >= LED_FULL_VEL then n := 10; 126 else n := vmax * 10 / LED_FULL_VEL; 127 end if; 128 case n is 129 when 0 => vel_out <= "0000000000"; 130 when 1 => vel_out <= "0000000001"; 131 when 2 => vel_out <= "0000000011"; 132 when 3 => vel_out <= "0000000111"; 133 when 4 => vel_out <= "0000001111"; 134 when 5 => vel_out <= "0000011111"; 135 when 6 => vel_out <= "0000111111"; 136 when 7 => vel_out <= "0001111111"; 137 when 8 => vel_out <= "0011111111"; 138 when 9 => vel_out <= "0111111111"; 139 when others => vel_out <= "1111111111"; 140 end case; 141 end process vel_bar; 142 143 char_width <= SIZE + ("000000" & squish) when squish_h = '0' 144 else SIZE - ("000000" & squish(3 downto 1)); 145 char_height <= SIZE - ("000000" & squish(3 downto 1)) when squish_h = '0' 146 else SIZE + ("000000" & squish); 147 148 physics : process 149 variable vx, vy : std_logic_vector(9 downto 0); 150 variable px, py : std_logic_vector(10 downto 0); 151 variable bounced : std_logic; 152 variable bounce_wall : std_logic; 153 variable bounce_speed : std_logic_vector(9 downto 0); 154 variable grounded : std_logic; 155 variable c_left, c_right, c_top, c_bot : std_logic_vector(10 downto 0); 156 variable c_prev_top, c_prev_bot, c_prev_left, c_prev_right : std_logic_vector(10 downto 0); 157 variable tcx, tcy, dcx, dcy : std_logic_vector(10 downto 0); 158 variable abs_vy_int : integer; 159 variable jforce_int : integer; 160 variable jforce_slv : std_logic_vector(9 downto 0); 161 variable slam_dist : std_logic_vector(10 downto 0); 162 variable slam_boost : std_logic_vector(9 downto 0); 163 variable x_overlap : boolean; 164 variable y_overlap : boolean; 165 begin 166 wait until vert_sync'event and vert_sync = '1'; 167 168 vx := vel_x; vy := vel_y; 169 bounced := '0'; bounce_wall := '0'; 170 bounce_speed := (others => '0'); 171 grounded := '0'; 172 173 -- == INPUT == 174 if key_w = '0' then jump_pressed <= '0'; end if; 175 176 -- Initial jump: force tapers as |vy| increases (inverse scaling) 177 if vy(9) = '1' then abs_vy_int := CONV_INTEGER((not vy) + 1); 178 else abs_vy_int := CONV_INTEGER(vy); end if; 179 if abs_vy_int <= JUMP_VEL_THR1 then jforce_int := JUMP_BASE; 180 elsif abs_vy_int <= JUMP_VEL_THR2 then jforce_int := 8; 181 elsif abs_vy_int <= JUMP_VEL_THR3 then jforce_int := 6; 182 else jforce_int := JUMP_MIN_FORCE; 183 end if; 184 jforce_slv := CONV_STD_LOGIC_VECTOR(jforce_int, 10); 185 186 if key_w = '1' and jump_pressed = '0' and on_ground = '1' then 187 vy := vy - jforce_slv; 188 jump_pressed <= '1'; 189 end if; 190 191 -- Trampoline: fixed boost (no inverse scaling) so energy accumulates each bounce 192 if key_w = '1' and jump_pressed = '1' and on_ground = '1' then 193 vy := vy - JUMP_BOUNCE; 194 end if; 195 196 -- S slam: rising edge in air = tap burst downward; hold to get landing boost 197 if key_s = '1' and prev_key_s = '0' and on_ground = '0' then 198 slam_start_y <= pos_y; 199 slam_held <= '1'; 200 slam_tap <= '1'; 201 vy := vy + SLAM_TAP_FORCE; 202 end if; 203 -- S released before landing: mark as tap, clear hold 204 if key_s = '0' and slam_held = '1' then slam_held <= '0'; end if; 205 prev_key_s <= key_s; 206 207 if key_a = '1' then 208 if on_ground = '1' then vx := vx - IMPULSE; else vx := vx - 2; end if; 209 end if; 210 if key_d = '1' then 211 if on_ground = '1' then vx := vx + IMPULSE; else vx := vx + 2; end if; 212 end if; 213 214 -- == GRAVITY == 215 vy := vy + GRAVITY; 216 217 -- == FRICTION: vel -= vel/4, min 1 == 218 if vx(9) = '0' then 219 if vx > 0 then 220 if vx(9 downto 2) = "00000000" then vx := vx - 1; 221 else vx := vx - ("00" & vx(9 downto 2)); end if; 222 end if; 223 else 224 if vx /= "0000000000" then 225 if vx(9 downto 2) = "11111111" then vx := vx + 1; 226 else vx := vx - ("11" & vx(9 downto 2)); end if; 227 end if; 228 end if; 229 230 -- == CLAMP velocities == 231 if vx(9) = '0' and vx > MAX_VEL_X then vx := MAX_VEL_X; end if; 232 if vx(9) = '1' and vx < (not MAX_VEL_X) + 1 then vx := (not MAX_VEL_X) + 1; end if; 233 if vy(9) = '0' and vy > 80 then vy := CONV_STD_LOGIC_VECTOR(80, 10); end if; 234 if vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(924, 10) then -- 924 = -100 235 vy := CONV_STD_LOGIC_VECTOR(924, 10); 236 end if; 237 238 -- == MOVE: sign-extend 10-bit velocity to 11-bit before adding == 239 px := pos_x + (vx(9) & vx); 240 py := pos_y + (vy(9) & vy); 241 242 -- == GROUND == 243 if py >= GROUND then 244 py := GROUND; 245 bounced := '1'; grounded := '1'; 246 bounce_speed := vy; 247 vy := (not vy) + 1; 248 if vy(9) = '1' then 249 vy := vy + ("000" & ((not vy(9 downto 3)) + 1)); 250 end if; 251 if vy(9) = '1' and vy >= CONV_STD_LOGIC_VECTOR(1022, 10) then 252 vy := (others => '0'); 253 elsif vy(9) = '0' then vy := (others => '0'); end if; 254 end if; 255 256 -- == CEILING == 257 if py >= UNDERFLOW or py <= CEILING then 258 py := CEILING; 259 bounced := '1'; 260 bounce_speed := (not vy) + 1; 261 vy := (not vy) + 1; 262 if vy(9) = '0' and vy > 1 then 263 vy := vy - ("000" & vy(9 downto 3)); 264 end if; 265 end if; 266 267 -- == LEFT WALL == 268 if px >= UNDERFLOW or px <= LEFT_WALL + SIZE11 then 269 px := LEFT_WALL + SIZE11; 270 bounced := '1'; bounce_wall := '1'; 271 bounce_speed := (not vx) + 1; 272 vx := (not vx) + 1; 273 if vx(9) = '0' and vx > 1 then 274 vx := vx - ("000" & vx(9 downto 3)); 275 end if; 276 end if; 277 278 -- == RIGHT WALL == 279 if px >= RIGHT_WALL - SIZE11 then 280 px := RIGHT_WALL - SIZE11; 281 bounced := '1'; bounce_wall := '1'; 282 bounce_speed := vx; 283 vx := (not vx) + 1; 284 if vx(9) = '1' then 285 vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); 286 end if; 287 if vx(9) = '1' and vx >= CONV_STD_LOGIC_VECTOR(1022, 10) then 288 vx := (others => '0'); 289 end if; 290 end if; 291 292 -- == OBSTACLE COLLISIONS (swept AABB) == 293 c_prev_top := pos_y - SIZE11; 294 c_prev_bot := pos_y + SIZE11; 295 c_prev_left := pos_x - SIZE11; 296 c_prev_right := pos_x + SIZE11; 297 298 for obs_i in 0 to OBS_COUNT-1 loop 299 c_left := px - SIZE11; 300 c_right := px + SIZE11; 301 c_top := py - SIZE11; 302 c_bot := py + SIZE11; 303 304 -- Swept overlap: strict on "was on correct side" to prevent wall sticking 305 if vx(9) = '0' then 306 x_overlap := (c_right >= OBS_L(obs_i)) and (c_prev_left < OBS_R(obs_i)); 307 else 308 x_overlap := (c_prev_right > OBS_L(obs_i)) and (c_left <= OBS_R(obs_i)); 309 end if; 310 311 -- Strict y: exclude characters at/below bottom face (down) or at/above top face (up) 312 if vy(9) = '0' then 313 y_overlap := (c_bot >= OBS_T(obs_i)) and (c_prev_top < OBS_B(obs_i)); 314 else 315 y_overlap := (c_prev_bot > OBS_T(obs_i)) and (c_top <= OBS_B(obs_i)); 316 end if; 317 318 if x_overlap and y_overlap then 319 320 if c_prev_bot <= OBS_T(obs_i) + SIZE11 and vy(9) = '0' then 321 py := OBS_T(obs_i) - SIZE11; grounded := '1'; 322 bounced := '1'; 323 vy := (not vy) + 1; 324 if vy(9) = '0' and vy > 1 then 325 vy := vy - ("000" & vy(9 downto 3)); 326 elsif vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(1022, 10) then 327 vy := vy + ("000" & ((not vy(9 downto 3)) + 1)); 328 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 331 vy := (others => '0'); 332 end if; 333 334 elsif c_prev_top >= OBS_B(obs_i) and vy(9) = '1' then 335 py := OBS_B(obs_i) + SIZE11; 336 bounced := '1'; 337 vy := (not vy) + 1; 338 if vy(9) = '0' and vy > 1 then 339 vy := vy - ("000" & vy(9 downto 3)); 340 end if; 341 342 elsif c_prev_right <= OBS_L(obs_i) and vx(9) = '0' then 343 px := OBS_L(obs_i) - SIZE11; 344 bounced := '1'; bounce_wall := '1'; 345 vx := (not vx) + 1; 346 if vx(9) = '0' and vx > 1 then 347 vx := vx - ("000" & vx(9 downto 3)); 348 elsif vx(9) = '1' and vx < CONV_STD_LOGIC_VECTOR(1022, 10) then 349 vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); 350 end if; 351 352 elsif c_prev_left >= OBS_R(obs_i) and vx(9) = '1' then 353 px := OBS_R(obs_i) + SIZE11; 354 bounced := '1'; bounce_wall := '1'; 355 vx := (not vx) + 1; 356 if vx(9) = '0' and vx > 1 then 357 vx := vx - ("000" & vx(9 downto 3)); 358 elsif vx(9) = '1' and vx < CONV_STD_LOGIC_VECTOR(1022, 10) then 359 vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); 360 end if; 361 362 else 363 if vy(9) = '0' then py := OBS_T(obs_i) - SIZE11; grounded := '1'; 364 else py := OBS_B(obs_i) + SIZE11; end if; 365 bounced := '1'; 366 vy := (not vy) + 1; 367 if vy(9) = '0' and vy > 1 then 368 vy := vy - ("000" & vy(9 downto 3)); 369 end if; 370 end if; 371 end if; 372 end loop; 373 374 -- == SLAM LANDING RESOLUTION == 375 if grounded = '1' and slam_tap = '1' then 376 if slam_held = '0' then 377 -- Tap slam: S released before landing — kill bounce, plant on surface 378 vy := (others => '0'); 379 else 380 -- Hold slam: distance-based upward boost 381 slam_dist := py - slam_start_y; 382 if slam_dist < SLAM_CLOSE_THR then 383 slam_boost := SLAM_BOOST_CLOSE; 384 elsif slam_dist < SLAM_MED_THR then 385 slam_boost := SLAM_BOOST_MED; 386 else 387 slam_boost := SLAM_BOOST_FAR; 388 end if; 389 vy := vy - slam_boost; 390 slam_held <= '0'; 391 end if; 392 slam_tap <= '0'; 393 end if; 394 395 -- == COMMIT == 396 vel_x <= vx; 397 vel_y <= vy; 398 pos_x <= px; 399 pos_y <= py; 400 401 -- == CAMERA: lag-follow character (1/8 of gap per frame, min 1px) == 402 -- Compute clamped target 403 if px < CONV_STD_LOGIC_VECTOR(320, 11) then 404 tcx := (others => '0'); 405 elsif px > CONV_STD_LOGIC_VECTOR(1180, 11) then 406 tcx := CONV_STD_LOGIC_VECTOR(860, 11); 407 else 408 tcx := px - CONV_STD_LOGIC_VECTOR(320, 11); 409 end if; 410 411 if py < CONV_STD_LOGIC_VECTOR(240, 11) then 412 tcy := (others => '0'); 413 elsif py > CONV_STD_LOGIC_VECTOR(1260, 11) then 414 tcy := CONV_STD_LOGIC_VECTOR(1020, 11); 415 else 416 tcy := py - CONV_STD_LOGIC_VECTOR(240, 11); 417 end if; 418 419 -- Slide camera toward target 420 if cam_x_sig < tcx then 421 dcx := tcx - cam_x_sig; 422 if dcx(10 downto 3) = "00000000" then 423 cam_x_sig <= cam_x_sig + 1; 424 else 425 cam_x_sig <= cam_x_sig + ("000" & dcx(10 downto 3)); 426 end if; 427 elsif cam_x_sig > tcx then 428 dcx := cam_x_sig - tcx; 429 if dcx(10 downto 3) = "00000000" then 430 cam_x_sig <= cam_x_sig - 1; 431 else 432 cam_x_sig <= cam_x_sig - ("000" & dcx(10 downto 3)); 433 end if; 434 end if; 435 436 if cam_y_sig < tcy then 437 dcy := tcy - cam_y_sig; 438 if dcy(10 downto 3) = "00000000" then 439 cam_y_sig <= cam_y_sig + 1; 440 else 441 cam_y_sig <= cam_y_sig + ("000" & dcy(10 downto 3)); 442 end if; 443 elsif cam_y_sig > tcy then 444 dcy := cam_y_sig - tcy; 445 if dcy(10 downto 3) = "00000000" then 446 cam_y_sig <= cam_y_sig - 1; 447 else 448 cam_y_sig <= cam_y_sig - ("000" & dcy(10 downto 3)); 449 end if; 450 end if; 451 452 -- == SQUISH == 453 if bounced = '1' and bounce_speed > 3 then 454 if bounce_speed >= 8 then squish <= "1000"; 455 else squish <= bounce_speed(3 downto 0); end if; 456 squish_h <= bounce_wall; 457 elsif squish > 0 then 458 squish <= squish - 1; 459 end if; 460 461 if grounded = '1' then on_ground <= '1'; 462 else on_ground <= '0'; end if; 463 464 end process physics; 465 466end behavior;