1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 666f4f3ced5003f24c048e6bacee8ffdb649a56e 467 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_start_y : std_logic_vector(10 downto 0) := (others => '0'); 97 signal prev_key_s : std_logic := '0'; 98 99 signal abs_vel_x : std_logic_vector(9 downto 0); 100 signal abs_vel_y : std_logic_vector(9 downto 0); 101 102begin 103 104 char_x <= pos_x; 105 char_y <= pos_y; 106 cam_x <= cam_x_sig; 107 cam_y <= cam_y_sig; 108 vel_x_out <= vel_x; 109 vel_y_out <= vel_y; 110 111 -- Absolute values of both velocity axes. 112 abs_vel_x <= (not vel_x) + 1 when vel_x(9) = '1' else vel_x; 113 abs_vel_y <= (not vel_y) + 1 when vel_y(9) = '1' else vel_y; 114 115 -- Thermometer-encode peak speed (max of |vx|,|vy|) → horizontal LED bar. 116 -- n LEDs lit from LSB up → 0000011111 for n=5, 1111111111 for n=10. 117 vel_bar : process(abs_vel_x, abs_vel_y) 118 variable vx, vy, vmax : integer; 119 variable n : integer range 0 to 10; 120 begin 121 vx := CONV_INTEGER(abs_vel_x); 122 vy := CONV_INTEGER(abs_vel_y); 123 if vx > vy then vmax := vx; else vmax := vy; end if; 124 if vmax >= LED_FULL_VEL then n := 10; 125 else n := vmax * 10 / LED_FULL_VEL; 126 end if; 127 case n is 128 when 0 => vel_out <= "0000000000"; 129 when 1 => vel_out <= "0000000001"; 130 when 2 => vel_out <= "0000000011"; 131 when 3 => vel_out <= "0000000111"; 132 when 4 => vel_out <= "0000001111"; 133 when 5 => vel_out <= "0000011111"; 134 when 6 => vel_out <= "0000111111"; 135 when 7 => vel_out <= "0001111111"; 136 when 8 => vel_out <= "0011111111"; 137 when 9 => vel_out <= "0111111111"; 138 when others => vel_out <= "1111111111"; 139 end case; 140 end process vel_bar; 141 142 char_width <= SIZE + ("000000" & squish) when squish_h = '0' 143 else SIZE - ("000000" & squish(3 downto 1)); 144 char_height <= SIZE - ("000000" & squish(3 downto 1)) when squish_h = '0' 145 else SIZE + ("000000" & squish); 146 147 physics : process 148 variable vx, vy : std_logic_vector(9 downto 0); 149 variable px, py : std_logic_vector(10 downto 0); 150 variable bounced : std_logic; 151 variable bounce_wall : std_logic; 152 variable bounce_speed : std_logic_vector(9 downto 0); 153 variable grounded : std_logic; 154 variable c_left, c_right, c_top, c_bot : std_logic_vector(10 downto 0); 155 variable c_prev_top, c_prev_bot, c_prev_left, c_prev_right : std_logic_vector(10 downto 0); 156 variable tcx, tcy, dcx, dcy : std_logic_vector(10 downto 0); 157 variable abs_vy_int : integer; 158 variable jforce_int : integer; 159 variable jforce_slv : std_logic_vector(9 downto 0); 160 variable slam_dist : std_logic_vector(10 downto 0); 161 variable slam_boost : std_logic_vector(9 downto 0); 162 begin 163 wait until vert_sync'event and vert_sync = '1'; 164 165 vx := vel_x; vy := vel_y; 166 bounced := '0'; bounce_wall := '0'; 167 bounce_speed := (others => '0'); 168 grounded := '0'; 169 170 -- == INPUT == 171 if key_w = '0' then jump_pressed <= '0'; end if; 172 173 -- Initial jump: force tapers as |vy| increases (inverse scaling) 174 if vy(9) = '1' then abs_vy_int := CONV_INTEGER((not vy) + 1); 175 else abs_vy_int := CONV_INTEGER(vy); end if; 176 if abs_vy_int <= JUMP_VEL_THR1 then jforce_int := JUMP_BASE; 177 elsif abs_vy_int <= JUMP_VEL_THR2 then jforce_int := 8; 178 elsif abs_vy_int <= JUMP_VEL_THR3 then jforce_int := 6; 179 else jforce_int := JUMP_MIN_FORCE; 180 end if; 181 jforce_slv := CONV_STD_LOGIC_VECTOR(jforce_int, 10); 182 183 if key_w = '1' and jump_pressed = '0' and on_ground = '1' then 184 vy := vy - jforce_slv; 185 jump_pressed <= '1'; 186 end if; 187 188 -- Trampoline: fixed boost (no inverse scaling) so energy accumulates each bounce 189 if key_w = '1' and jump_pressed = '1' and on_ground = '1' then 190 vy := vy - JUMP_BOUNCE; 191 end if; 192 193 -- S slam: rising edge in air = tap burst downward; hold to get landing boost 194 if key_s = '1' and prev_key_s = '0' and on_ground = '0' then 195 slam_start_y <= pos_y; 196 slam_held <= '1'; 197 vy := vy + SLAM_TAP_FORCE; 198 end if; 199 if key_s = '0' then slam_held <= '0'; end if; 200 prev_key_s <= key_s; 201 202 if key_a = '1' then 203 if on_ground = '1' then vx := vx - IMPULSE; else vx := vx - 2; end if; 204 end if; 205 if key_d = '1' then 206 if on_ground = '1' then vx := vx + IMPULSE; else vx := vx + 2; end if; 207 end if; 208 209 -- == GRAVITY == 210 vy := vy + GRAVITY; 211 212 -- == FRICTION: vel -= vel/4, min 1 == 213 if vx(9) = '0' then 214 if vx > 0 then 215 if vx(9 downto 2) = "00000000" then vx := vx - 1; 216 else vx := vx - ("00" & vx(9 downto 2)); end if; 217 end if; 218 else 219 if vx /= "0000000000" then 220 if vx(9 downto 2) = "11111111" then vx := vx + 1; 221 else vx := vx - ("11" & vx(9 downto 2)); end if; 222 end if; 223 end if; 224 225 -- == CLAMP velocities == 226 if vx(9) = '0' and vx > MAX_VEL_X then vx := MAX_VEL_X; end if; 227 if vx(9) = '1' and vx < (not MAX_VEL_X) + 1 then vx := (not MAX_VEL_X) + 1; end if; 228 if vy(9) = '0' and vy > 80 then vy := CONV_STD_LOGIC_VECTOR(80, 10); end if; 229 if vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(924, 10) then -- 924 = -100 230 vy := CONV_STD_LOGIC_VECTOR(924, 10); 231 end if; 232 233 -- == MOVE: sign-extend 10-bit velocity to 11-bit before adding == 234 px := pos_x + (vx(9) & vx); 235 py := pos_y + (vy(9) & vy); 236 237 -- == GROUND == 238 if py >= GROUND then 239 py := GROUND; 240 bounced := '1'; grounded := '1'; 241 bounce_speed := vy; 242 vy := (not vy) + 1; 243 if vy(9) = '1' then 244 vy := vy + ("000" & ((not vy(9 downto 3)) + 1)); 245 end if; 246 if vy(9) = '1' and vy >= CONV_STD_LOGIC_VECTOR(1022, 10) then 247 vy := (others => '0'); 248 elsif vy(9) = '0' then vy := (others => '0'); end if; 249 end if; 250 251 -- == CEILING == 252 -- Underflow detection: py >= 2000 means it wrapped below zero. 253 -- (GROUND=1480, RIGHT_WALL=1492 are both < 2000, so no false triggers.) 254 if py >= UNDERFLOW or py <= CEILING then 255 py := CEILING; 256 bounced := '1'; 257 bounce_speed := (not vy) + 1; 258 vy := (not vy) + 1; 259 if vy(9) = '0' and vy > 1 then 260 vy := vy - ("000" & vy(9 downto 3)); 261 end if; 262 end if; 263 264 -- == LEFT WALL == 265 -- Same underflow fix: px >= 2000 means wrapped below zero. 266 if px >= UNDERFLOW or px <= LEFT_WALL + SIZE11 then 267 px := LEFT_WALL + SIZE11; 268 bounced := '1'; bounce_wall := '1'; 269 bounce_speed := (not vx) + 1; 270 vx := (not vx) + 1; 271 if vx(9) = '0' and vx > 1 then 272 vx := vx - ("000" & vx(9 downto 3)); 273 end if; 274 end if; 275 276 -- == RIGHT WALL == 277 if px >= RIGHT_WALL - SIZE11 then 278 px := RIGHT_WALL - SIZE11; 279 bounced := '1'; bounce_wall := '1'; 280 bounce_speed := vx; 281 vx := (not vx) + 1; 282 if vx(9) = '1' then 283 vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); 284 end if; 285 if vx(9) = '1' and vx >= CONV_STD_LOGIC_VECTOR(1022, 10) then 286 vx := (others => '0'); 287 end if; 288 end if; 289 290 -- == OBSTACLE COLLISIONS == 291 -- Use previous-frame bounds to detect which face was entered. 292 -- This avoids the overlap-axis bug where a fast horizontal move into 293 -- a thin platform's side gets incorrectly resolved as a vertical hit. 294 c_prev_top := pos_y - SIZE11; 295 c_prev_bot := pos_y + SIZE11; 296 c_prev_left := pos_x - SIZE11; 297 c_prev_right := pos_x + SIZE11; 298 299 for obs_i in 0 to OBS_COUNT-1 loop 300 c_left := px - SIZE11; 301 c_right := px + SIZE11; 302 c_top := py - SIZE11; 303 c_bot := py + SIZE11; 304 305 -- 1. Swept Horizontal Overlap 306 if vx(9) = '0' then -- moving right 307 x_overlap := (c_right >= OBS_L(obs_i)) and (c_prev_left <= OBS_R(obs_i)); 308 else -- moving left 309 x_overlap := (c_prev_right >= OBS_L(obs_i)) and (c_left <= OBS_R(obs_i)); 310 end if; 311 312 -- 2. Swept Vertical Overlap 313 if vy(9) = '0' then -- moving down (gravity is positive) 314 y_overlap := (c_bot >= OBS_T(obs_i)) and (c_prev_top <= OBS_B(obs_i)); 315 else -- moving up 316 y_overlap := (c_prev_bot >= OBS_T(obs_i)) and (c_top <= OBS_B(obs_i)); 317 end if; 318 319 -- 3. Check collision using the swept bounds 320 if x_overlap and y_overlap then 321 322 if c_prev_bot <= OBS_T(obs_i) then 323 -- Entered from top → land on surface 324 py := OBS_T(obs_i) - SIZE11; grounded := '1'; 325 bounced := '1'; 326 vy := (not vy) + 1; 327 if vy(9) = '0' and vy > 1 then 328 vy := vy - ("000" & vy(9 downto 3)); 329 elsif vy(9) = '1' and vy < CONV_STD_LOGIC_VECTOR(1022, 10) then 330 vy := vy + ("000" & ((not vy(9 downto 3)) + 1)); 331 end if; 332 if vy(9) = '0' and vy < 2 then vy := (others => '0'); end if; 333 if vy(9) = '1' and vy >= CONV_STD_LOGIC_VECTOR(1022, 10) then 334 vy := (others => '0'); 335 end if; 336 337 elsif c_prev_top >= OBS_B(obs_i) then 338 -- Entered from bottom → bump head on underside 339 py := OBS_B(obs_i) + SIZE11; 340 bounced := '1'; 341 vy := (not vy) + 1; 342 if vy(9) = '0' and vy > 1 then 343 vy := vy - ("000" & vy(9 downto 3)); 344 end if; 345 346 elsif c_prev_right <= OBS_L(obs_i) then 347 -- Entered from left → bounce off left face 348 px := OBS_L(obs_i) - SIZE11; 349 bounced := '1'; bounce_wall := '1'; 350 vx := (not vx) + 1; 351 if vx(9) = '0' and vx > 1 then 352 vx := vx - ("000" & vx(9 downto 3)); 353 elsif vx(9) = '1' and vx < CONV_STD_LOGIC_VECTOR(1022, 10) then 354 vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); 355 end if; 356 357 elsif c_prev_left >= OBS_R(obs_i) then 358 -- Entered from right → bounce off right face 359 px := OBS_R(obs_i) + SIZE11; 360 bounced := '1'; bounce_wall := '1'; 361 vx := (not vx) + 1; 362 if vx(9) = '0' and vx > 1 then 363 vx := vx - ("000" & vx(9 downto 3)); 364 elsif vx(9) = '1' and vx < CONV_STD_LOGIC_VECTOR(1022, 10) then 365 vx := vx + ("000" & ((not vx(9 downto 3)) + 1)); 366 end if; 367 368 else 369 -- Corner / spawn-inside fallback: resolve by velocity direction 370 if vy(9) = '0' then py := OBS_T(obs_i) - SIZE11; grounded := '1'; 371 else py := OBS_B(obs_i) + SIZE11; end if; 372 bounced := '1'; 373 vy := (not vy) + 1; 374 if vy(9) = '0' and vy > 1 then 375 vy := vy - ("000" & vy(9 downto 3)); 376 end if; 377 end if; 378 end if; 379 end loop; 380 381 -- == HOLD-SLAM LANDING BOOST == 382 -- Bigger boost when slam started closer to the surface 383 if grounded = '1' and slam_held = '1' then 384 slam_dist := py - slam_start_y; 385 if slam_dist < SLAM_CLOSE_THR then 386 slam_boost := SLAM_BOOST_CLOSE; 387 elsif slam_dist < SLAM_MED_THR then 388 slam_boost := SLAM_BOOST_MED; 389 else 390 slam_boost := SLAM_BOOST_FAR; 391 end if; 392 vy := vy - slam_boost; 393 slam_held <= '0'; 394 end if; 395 396 -- == COMMIT == 397 vel_x <= vx; 398 vel_y <= vy; 399 pos_x <= px; 400 pos_y <= py; 401 402 -- == CAMERA: lag-follow character (1/8 of gap per frame, min 1px) == 403 -- Compute clamped target 404 if px < CONV_STD_LOGIC_VECTOR(320, 11) then 405 tcx := (others => '0'); 406 elsif px > CONV_STD_LOGIC_VECTOR(1180, 11) then 407 tcx := CONV_STD_LOGIC_VECTOR(860, 11); 408 else 409 tcx := px - CONV_STD_LOGIC_VECTOR(320, 11); 410 end if; 411 412 if py < CONV_STD_LOGIC_VECTOR(240, 11) then 413 tcy := (others => '0'); 414 elsif py > CONV_STD_LOGIC_VECTOR(1260, 11) then 415 tcy := CONV_STD_LOGIC_VECTOR(1020, 11); 416 else 417 tcy := py - CONV_STD_LOGIC_VECTOR(240, 11); 418 end if; 419 420 -- Slide camera toward target 421 if cam_x_sig < tcx then 422 dcx := tcx - cam_x_sig; 423 if dcx(10 downto 3) = "00000000" then 424 cam_x_sig <= cam_x_sig + 1; 425 else 426 cam_x_sig <= cam_x_sig + ("000" & dcx(10 downto 3)); 427 end if; 428 elsif cam_x_sig > tcx then 429 dcx := cam_x_sig - tcx; 430 if dcx(10 downto 3) = "00000000" then 431 cam_x_sig <= cam_x_sig - 1; 432 else 433 cam_x_sig <= cam_x_sig - ("000" & dcx(10 downto 3)); 434 end if; 435 end if; 436 437 if cam_y_sig < tcy then 438 dcy := tcy - cam_y_sig; 439 if dcy(10 downto 3) = "00000000" then 440 cam_y_sig <= cam_y_sig + 1; 441 else 442 cam_y_sig <= cam_y_sig + ("000" & dcy(10 downto 3)); 443 end if; 444 elsif cam_y_sig > tcy then 445 dcy := cam_y_sig - tcy; 446 if dcy(10 downto 3) = "00000000" then 447 cam_y_sig <= cam_y_sig - 1; 448 else 449 cam_y_sig <= cam_y_sig - ("000" & dcy(10 downto 3)); 450 end if; 451 end if; 452 453 -- == SQUISH == 454 if bounced = '1' and bounce_speed > 3 then 455 if bounce_speed >= 8 then squish <= "1000"; 456 else squish <= bounce_speed(3 downto 0); end if; 457 squish_h <= bounce_wall; 458 elsif squish > 0 then 459 squish <= squish - 1; 460 end if; 461 462 if grounded = '1' then on_ground <= '1'; 463 else on_ground <= '0'; end if; 464 465 end process physics; 466 467end behavior;