diff --git a/physics_engine.vhd b/physics_engine.vhd index ddb42a1..71f35e8 100644 --- a/physics_engine.vhd +++ b/physics_engine.vhd @@ -93,6 +93,7 @@ architecture behavior of physics_engine is signal on_ground : std_logic := '0'; signal jump_pressed : std_logic := '0'; signal slam_held : std_logic := '0'; + signal slam_tap : std_logic := '0'; -- S pressed then released before landing signal slam_start_y : std_logic_vector(10 downto 0) := (others => '0'); signal prev_key_s : std_logic := '0'; @@ -159,6 +160,8 @@ begin variable jforce_slv : std_logic_vector(9 downto 0); variable slam_dist : std_logic_vector(10 downto 0); variable slam_boost : std_logic_vector(9 downto 0); + variable x_overlap : boolean; + variable y_overlap : boolean; begin wait until vert_sync'event and vert_sync = '1'; @@ -194,9 +197,11 @@ begin if key_s = '1' and prev_key_s = '0' and on_ground = '0' then slam_start_y <= pos_y; slam_held <= '1'; + slam_tap <= '1'; vy := vy + SLAM_TAP_FORCE; end if; - if key_s = '0' then slam_held <= '0'; end if; + -- S released before landing: mark as tap, clear hold + if key_s = '0' and slam_held = '1' then slam_held <= '0'; end if; prev_key_s <= key_s; if key_a = '1' then @@ -249,8 +254,6 @@ begin end if; -- == CEILING == - -- Underflow detection: py >= 2000 means it wrapped below zero. - -- (GROUND=1480, RIGHT_WALL=1492 are both < 2000, so no false triggers.) if py >= UNDERFLOW or py <= CEILING then py := CEILING; bounced := '1'; @@ -262,7 +265,6 @@ begin end if; -- == LEFT WALL == - -- Same underflow fix: px >= 2000 means wrapped below zero. if px >= UNDERFLOW or px <= LEFT_WALL + SIZE11 then px := LEFT_WALL + SIZE11; bounced := '1'; bounce_wall := '1'; @@ -287,10 +289,7 @@ begin end if; end if; - -- == OBSTACLE COLLISIONS == - -- Use previous-frame bounds to detect which face was entered. - -- This avoids the overlap-axis bug where a fast horizontal move into - -- a thin platform's side gets incorrectly resolved as a vertical hit. + -- == OBSTACLE COLLISIONS (swept AABB) == c_prev_top := pos_y - SIZE11; c_prev_bot := pos_y + SIZE11; c_prev_left := pos_x - SIZE11; @@ -302,25 +301,23 @@ begin c_top := py - SIZE11; c_bot := py + SIZE11; - -- 1. Swept Horizontal Overlap - if vx(9) = '0' then -- moving right + -- Swept horizontal overlap: union of prev and new extents along motion axis + if vx(9) = '0' then x_overlap := (c_right >= OBS_L(obs_i)) and (c_prev_left <= OBS_R(obs_i)); - else -- moving left + else x_overlap := (c_prev_right >= OBS_L(obs_i)) and (c_left <= OBS_R(obs_i)); end if; - -- 2. Swept Vertical Overlap - if vy(9) = '0' then -- moving down (gravity is positive) + -- Swept vertical overlap + if vy(9) = '0' then y_overlap := (c_bot >= OBS_T(obs_i)) and (c_prev_top <= OBS_B(obs_i)); - else -- moving up + else y_overlap := (c_prev_bot >= OBS_T(obs_i)) and (c_top <= OBS_B(obs_i)); end if; - -- 3. Check collision using the swept bounds if x_overlap and y_overlap then if c_prev_bot <= OBS_T(obs_i) then - -- Entered from top → land on surface py := OBS_T(obs_i) - SIZE11; grounded := '1'; bounced := '1'; vy := (not vy) + 1; @@ -335,7 +332,6 @@ begin end if; elsif c_prev_top >= OBS_B(obs_i) then - -- Entered from bottom → bump head on underside py := OBS_B(obs_i) + SIZE11; bounced := '1'; vy := (not vy) + 1; @@ -344,7 +340,6 @@ begin end if; elsif c_prev_right <= OBS_L(obs_i) then - -- Entered from left → bounce off left face px := OBS_L(obs_i) - SIZE11; bounced := '1'; bounce_wall := '1'; vx := (not vx) + 1; @@ -355,7 +350,6 @@ begin end if; elsif c_prev_left >= OBS_R(obs_i) then - -- Entered from right → bounce off right face px := OBS_R(obs_i) + SIZE11; bounced := '1'; bounce_wall := '1'; vx := (not vx) + 1; @@ -366,7 +360,6 @@ begin end if; else - -- Corner / spawn-inside fallback: resolve by velocity direction if vy(9) = '0' then py := OBS_T(obs_i) - SIZE11; grounded := '1'; else py := OBS_B(obs_i) + SIZE11; end if; bounced := '1'; @@ -378,19 +371,25 @@ begin end if; end loop; - -- == HOLD-SLAM LANDING BOOST == - -- Bigger boost when slam started closer to the surface - if grounded = '1' and slam_held = '1' then - slam_dist := py - slam_start_y; - if slam_dist < SLAM_CLOSE_THR then - slam_boost := SLAM_BOOST_CLOSE; - elsif slam_dist < SLAM_MED_THR then - slam_boost := SLAM_BOOST_MED; + -- == SLAM LANDING RESOLUTION == + if grounded = '1' and slam_tap = '1' then + if slam_held = '0' then + -- Tap slam: S released before landing — kill bounce, plant on surface + vy := (others => '0'); else - slam_boost := SLAM_BOOST_FAR; + -- Hold slam: distance-based upward boost + slam_dist := py - slam_start_y; + if slam_dist < SLAM_CLOSE_THR then + slam_boost := SLAM_BOOST_CLOSE; + elsif slam_dist < SLAM_MED_THR then + slam_boost := SLAM_BOOST_MED; + else + slam_boost := SLAM_BOOST_FAR; + end if; + vy := vy - slam_boost; + slam_held <= '0'; end if; - vy := vy - slam_boost; - slam_held <= '0'; + slam_tap <= '0'; end if; -- == COMMIT == diff --git a/visualizer.py b/visualizer.py index 3fdfb3f..6cbb8be 100644 --- a/visualizer.py +++ b/visualizer.py @@ -91,6 +91,7 @@ def main(): on_ground = False jump_pressed = False slam_held = False + slam_tap = False # S pressed and released before landing — suppress bounce slam_start_y = 0 prev_key_s = False @@ -116,7 +117,7 @@ def main(): char_x, char_y = 100, 200 vel_x = vel_y = to_unsigned(0) squish = 0; squish_h = False; on_ground = False; jump_pressed = False - slam_held = False; slam_start_y = 0; prev_key_s = False + slam_held = False; slam_tap = False; slam_start_y = 0; prev_key_s = False trail.clear() elif event.type == pygame.KEYUP: if event.key == pygame.K_w: keys_held['w'] = False @@ -156,8 +157,10 @@ def main(): if keys_held['s'] and not prev_key_s and not on_ground: slam_start_y = char_y slam_held = True + slam_tap = True vy = (vy + SLAM_TAP_FORCE) & MASK - if not keys_held['s']: + if not keys_held['s'] and slam_held: + # S released before landing — mark as tap, no hold boost slam_held = False prev_key_s = keys_held['s'] @@ -208,15 +211,12 @@ def main(): # --- Ground bounce --- if py >= GROUND: py = GROUND - bounced = True - grounded = True + bounced = True; grounded = True bounce_speed = abs(to_signed(vy)) vy = negate(vy) svy = to_signed(vy) - # svy is now negative (upward), apply energy loss on magnitude if svy < -1: - svy += (-svy) >> BOUNCE_SHIFT # reduce magnitude - # Kill tiny bounces + svy += (-svy) >> BOUNCE_SHIFT if abs(svy) < 2: svy = 0 vy = to_unsigned(svy) @@ -231,49 +231,31 @@ def main(): if abs(svy) > 1: svy_abs = abs(svy) loss = svy_abs >> BOUNCE_SHIFT - if svy > 0: - svy -= loss - else: - svy += loss + if svy > 0: svy -= loss + else: svy += loss vy = to_unsigned(svy) - # --- Left wall bounce --- - if px <= LEFT_WALL + SIZE: - px = LEFT_WALL + SIZE - bounced = True; bounce_wall = True - bounce_speed = abs(to_signed(vx)) - vx = negate(vx) - svx = to_signed(vx) - if svx > 1: - svx -= svx >> BOUNCE_SHIFT - vx = to_unsigned(svx) - - # --- Right wall bounce --- - if px >= RIGHT_WALL - SIZE: - px = RIGHT_WALL - SIZE - bounced = True; bounce_wall = True - bounce_speed = abs(to_signed(vx)) - vx = negate(vx) - svx = to_signed(vx) - if svx < -1: - svx += (-svx) >> BOUNCE_SHIFT - vx = to_unsigned(svx) - - # --- Obstacle collisions (entry-face detection using prev position) --- + # --- Obstacle collisions (swept AABB — mirrors VHDL swept check) --- prev_top = char_y - SIZE prev_bot = char_y + SIZE prev_left = char_x - SIZE prev_right = char_x + SIZE for (ol, ot, orr, ob) in OBSTACLES: - c_left = px - SIZE - c_right = px + SIZE - c_top = py - SIZE - c_bot = py + SIZE + c_left = px - SIZE; c_right = px + SIZE + c_top = py - SIZE; c_bot = py + SIZE + + if to_signed(vx) >= 0: + x_overlap = c_right >= ol and prev_left <= orr + else: + x_overlap = prev_right >= ol and c_left <= orr + if to_signed(vy) >= 0: + y_overlap = c_bot >= ot and prev_top <= ob + else: + y_overlap = prev_bot >= ot and c_top <= ob - if c_right >= ol and c_left <= orr and c_bot >= ot and c_top <= ob: + if x_overlap and y_overlap: if prev_bot <= ot: - # Entered from top py = ot - SIZE; grounded = True bounced = True bounce_speed = abs(to_signed(vy)) @@ -285,7 +267,6 @@ def main(): if abs(svy) < 2: svy = 0 vy = to_unsigned(svy) elif prev_top >= ob: - # Entered from bottom py = ob + SIZE bounced = True vy = negate(vy) @@ -294,27 +275,24 @@ def main(): svy -= svy >> BOUNCE_SHIFT vy = to_unsigned(svy) elif prev_right <= ol: - # Entered from left px = ol - SIZE bounced = True; bounce_wall = True bounce_speed = abs(to_signed(vx)) vx = negate(vx) svx = to_signed(vx) - if svx > 1: svx -= svx >> BOUNCE_SHIFT - elif svx < -1: svx += (-svx) >> BOUNCE_SHIFT + if svx > 1: svx -= svx >> BOUNCE_SHIFT + elif svx < -1: svx += (-svx) >> BOUNCE_SHIFT vx = to_unsigned(svx) elif prev_left >= orr: - # Entered from right px = orr + SIZE bounced = True; bounce_wall = True bounce_speed = abs(to_signed(vx)) vx = negate(vx) svx = to_signed(vx) - if svx > 1: svx -= svx >> BOUNCE_SHIFT - elif svx < -1: svx += (-svx) >> BOUNCE_SHIFT + if svx > 1: svx -= svx >> BOUNCE_SHIFT + elif svx < -1: svx += (-svx) >> BOUNCE_SHIFT vx = to_unsigned(svx) else: - # Corner/inside fallback svy_now = to_signed(vy) if svy_now >= 0: py = ot - SIZE; grounded = True else: py = ob + SIZE @@ -326,8 +304,35 @@ def main(): svy = svy - loss if svy > 0 else svy + loss vy = to_unsigned(svy) + # --- Left wall bounce --- + if px <= LEFT_WALL + SIZE: + px = LEFT_WALL + SIZE + bounced = True; bounce_wall = True + bounce_speed = abs(to_signed(vx)) + vx = negate(vx) + svx = to_signed(vx) + if svx > 1: + svx -= svx >> BOUNCE_SHIFT + vx = to_unsigned(svx) + + # --- Right wall bounce --- + if px >= RIGHT_WALL - SIZE: + px = RIGHT_WALL - SIZE + bounced = True; bounce_wall = True + bounce_speed = abs(to_signed(vx)) + vx = negate(vx) + svx = to_signed(vx) + if svx < -1: + svx += (-svx) >> BOUNCE_SHIFT + vx = to_unsigned(svx) + + # Tap slam: S was released before landing — kill bounce, plant on surface + if grounded and slam_tap and not slam_held: + vy = to_unsigned(0) + slam_tap = False + # Hold-slam landing boost: bigger boost when slam started closer to the surface - if grounded and slam_held: + elif grounded and slam_held: dist = py - slam_start_y if dist < SLAM_CLOSE_THR: slam_boost_val = SLAM_BOOST_CLOSE @@ -337,6 +342,7 @@ def main(): slam_boost_val = SLAM_BOOST_FAR vy = (vy - slam_boost_val) & MASK slam_held = False + slam_tap = False # Commit char_x = px