import sys
# THE WEB-EDITOR FIX: Re-inject the system argument if the web editor forgets it on reload.
# Without this, the web editor crashes when trying to load the game file multiple times.
if not sys.argv:
    sys.argv.append("game.py")

import pyxel
import math
import random

# ==========================================
# --- GLOBALS & CONSTANTS ---
# These are the fundamental rules of our game world.
# ==========================================
SCREEN_WIDTH = 256
SCREEN_HEIGHT = 256
GRAVITY = 0.6               # How fast things fall down
FLOOR_Y = SCREEN_HEIGHT - 32 # The Y-coordinate for the ground
PLAYER_SPEED = 3            # How fast the Warlock moves left/right
JUMP_STRENGTH = -9          # Negative Y moves the player UP in Pyxel

class Game:
    """
    MAIN GAME CLASS
    This class handles everything: initializing the engine, running the game loop (Update),
    and drawing everything to the screen (Draw).
    """
    def __init__(self):
        # 1. Initialize the Pyxel engine with our screen size and window title
        pyxel.init(SCREEN_WIDTH, SCREEN_HEIGHT, title="The Malevolent Witch")
        pyxel.mouse(True) # Make the mouse cursor visible for aiming
        
        # 2. State Machine: Tracks what screen we are on
        # 0 = Main Menu | 1 = Upgrades | 2 = Dev Panel | 3 = Gameplay
        # 4 = Game Over | 5 = Victory Screen | 6 = Settings | 7 = Credits
        self.state = 0
        self.level = 1
        self.coins = 0
        
        # 3. Audio Settings (Volume out of 7)
        self.music_vol = 3 
        self.sfx_vol = 6
        
        # 4. RPG Progression Stats
        self.damage_lv = 1
        self.proj_speed_lv = 1
        self.hp_lv = 1
        self.max_hp = 100
        
        self.unlocked_spells = []
        self.active_spell = None
        
        # 5. Developer / God Mode Settings
        self.dev_unlocked = False
        self.immortal = False
        self.dev_dmg_slider = 0.1 
        self.slider_drag = False
        
        # 6. Visual Effects Lists (Particles, explosions, floating numbers)
        self.particles = []
        self.explosions = [] 
        self.flames = [] 
        self.floating_texts = []      
        self.shake = 0    # Camera shake intensity
        self.flash = 0    # White screen flash intensity
        
        # Smooth Health Bars (For visual animation)
        self.p_hp_disp = 100
        self.boss_hp_disp = 100
        
        # 7. Initialize our background elements and audio
        self.init_environment()
        
        # 8. Credits setup
        self.credits_list = [
            "THE MALEVOLENT WITCH", "",
            "Game Idea: Jashan and Tony",
            "Main Coder: Gemini and Tony",
            "Co-Coder: Chatgbt",
            "Most Useful Coder: Gemini",
            "Visionary Overlord: Jashan",
            "Executive Mastermind: Jashan",
            "Artist: Gemini",
            "Composer: Gemini",
            "Credits Maker: Gemini", 
            "Debugger: Gemini and Jashan",
            "Emotional Support: Tony",
            "Manager: Jashan and Tony",
            "Investors: Jashan and Tony",
            "Game Tester: Jashan",
            "Game Approver: Tony"
            "Chief Procrastinator: Tony",
            "Janitor: Tony",
            "Maid : Jashan",
            "Professional Gooner: Jashan"
            "Chief executive gooning officer : Jashan",
            "", "THANK YOU FOR PLAYING!"
        ]
        self.credits_y = 256 # Starts off the bottom of the screen
        
        self.init_audio()
        self.reset_game()
        
        # 9. Start the Engine Loop!
        pyxel.run(self.update, self.draw)

    # ==========================================
    # --- ENVIRONMENT SETUP ---
    # Creates the background visuals (rain, leaves, birds)
    # ==========================================
    def init_environment(self):
        # Regular Gameplay Ambience
        self.rain = [[random.randint(0, 256), random.randint(0, 256)] for _ in range(60)]
        self.water_splashes = []
        self.mist = [[random.randint(0, 256), random.randint(155, 170), random.uniform(0.2, 0.5)] for _ in range(12)]
        
        # Detailed regular green leaves: [x, y, leaf_shape_type]
        self.leaves = [[random.randint(0, 300), random.randint(0, 200), random.randint(0, 2)] for _ in range(25)]
        
        self.whale = {"active": False, "x": 0, "y": 0, "vx": 0, "vy": 0}
        self.bird = {"x": -20, "y": 50, "target_y": 50, "swooping": False}

        # Ending Scene (Level 7 Defeated) Ambience Elements
        self.flowers = []
        for _ in range(180):
            self.flowers.append({
                "x": random.randint(0, 256), "y": random.randint(180, 250),
                "color": random.choice([7, 8, 9, 10, 11, 12, 14]), "size": random.randint(1, 2),
                "sway_offset": random.uniform(0, math.pi * 2)
            })
            
        self.fireflies = [[random.randint(10, 246), random.randint(100, 170), random.randint(0, 30), random.uniform(-0.3, 0.3)] for _ in range(8)]
        
        self.butterflies = []
        for _ in range(10):
            self.butterflies.append({
                "x": random.randint(30, 226), "y": random.randint(160, 220),
                "vx": random.uniform(-1, 1), "vy": random.uniform(-0.5, 0.5),
                "color": random.choice([8, 9, 10, 12]), "flap_timer": 0
            })
            
        self.bird_flock = []
        for _ in range(6):
            self.bird_flock.append({
                "x": random.randint(-100, -20), "y": random.randint(20, 80),
                "speed": random.uniform(0.3, 0.6), "w_offset": random.uniform(0, 10)
            })
            
        # Autumn leaves logic: Uses the same 3 shapes as normal leaves, but Autumn colors
        self.autumn_leaves = []
        for _ in range(25):
            # Tuples representing (Core Color, Accent Color)
            # Lots of reds/oranges/browns, very little green
            c_pairs = [(9, 8), (10, 9), (8, 4), (4, 9), (11, 3)] 
            pair = random.choices(c_pairs, weights=[4, 3, 3, 2, 1])[0]
            # [x, y, sway_speed, shape_type, color1, color2]
            self.autumn_leaves.append([random.randint(0, 256), random.randint(-20, 230), random.uniform(0.5, 1.2), random.randint(0, 2), pair[0], pair[1]])
            
        self.wind_timer = 0
        self.mage_blood_drips = []
        self.blood_timer = 0
        self.dove = {"x": -40, "y": 30, "state": "flying"}

    # ==========================================
    # --- AUDIO ENGINE ---
    # Injects musical notes and sound effects into Pyxel's sound channels
    # ==========================================
    def init_audio(self):
        sv = str(self.sfx_vol)
        mv = str(max(0, self.music_vol - 2)) 
        
        # Channel 0: Melody | Channel 1: Harmony | Channel 2: Bass/Drums
        m0 = ("a1 c2 e2 g1 b1 d2 f1 a1 c2 "
              "c1 e1 g1 d1 f1 a1 g1 b1 d2 e1 g#1 b1")
        pyxel.sound(0).set(m0, "p", mv, "v", 240) 
        
        m1 = ("c2 r e2 r b1 r d2 r a1 r c2 r e1 "
              "r g1 r f1 r a1 r d1 r f1 r g#1 r b1 r")
        pyxel.sound(1).set(m1, "s", mv, "v", 240) 
        
        m2 = ("a2 r c3 r g2 r b2 r f2 r a2 r c2 r e2 "
              "r d2 r f2 r g2 r b2 r e2 r g#2 r")
        pyxel.sound(2).set(m2, "t", mv, "n", 240) 

        # Sound Effects
        pyxel.sound(50).set("a3a2", "p", sv, "f", 4)      # Shoot spell
        pyxel.sound(51).set("a0a0", "n", sv, "f", 8)      # Explosion
        pyxel.sound(52).set("c2e2", "s", sv, "f", 4)      # Jump
        pyxel.sound(53).set("c4e4", "t", sv, "n", 3)      # Coin / Upgrade
        pyxel.sound(54).set("c1", "n", sv, "f", 6)        # Player hurt
        pyxel.sound(55).set("c2", "n", sv, "f", 2)        # Enemy hit
        pyxel.sound(56).set("c2e2g2c3", "s", sv, "f", 12) # Phase transition
        pyxel.sound(57).set("a1a0", "n", sv, "f", 10)     # Heavy swing / Punch
        pyxel.sound(58).set("e4e4e4e4e4e4", "n", sv, "f", 25) # Witch Final Phase
        pyxel.sound(60).set("c2", "t", sv, "f", 3)        # Blocked
        pyxel.sound(61).set("a0a0a0", "s", sv, "f", 20)   # Skeleton Spawn
        pyxel.sound(62).set("a0a0", "n", sv, "f", 15)     # Lava Geyser / Earth Spike
        pyxel.sound(63).set("a0c0e0", "n", sv, "f", 15)   # Heavy Fireball explosion

    def play_music(self):
        pyxel.stop() # Stop all sounds to prevent overlapping
        if self.music_vol == 0: return 
        mv = str(max(0, self.music_vol - 2)) 
        
        # Load different songs based on current game level/state
        if self.state == 7:
            pyxel.play(0, [0], loop=True); pyxel.play(1, [1], loop=True); pyxel.play(2, [2], loop=True)
        elif getattr(self, 'witch_darkness', False) or self.level == 0:
            return # Dead silence for tension
        elif self.level in [1, 2]:
            s0 = ("d2f2a2d3 d2f2a2d3 a1c2e2a2 a1c2e2a2 f1a1c2f2 f1a1c2f2 g1a#1d2g2 g1a#1d2g2 d2f2a2f3 d2f2a2f3 a1c2e2c3 a1c2e2c3 f1a1d2f2 f1a1d2f2 g1a#1d2g2 a1c#2e2a2")
            pyxel.sound(0).set(s0, "p", mv, "v", 50) 
            s1 = ("d1rrrrrr d1rrrrrr a0rrrrrr a0rrrrrr f0rrrrrr f0rrrrrr g0rrrrrr g0rrrrrr d1rrd1rr d1rrd1rr a0rra0rr a0rra0rr f0rrf0rr f0rrf0rr g0rrg0rr a0rra0rr")
            pyxel.sound(1).set(s1, "s", mv, "n", 50)
            s2 = ("c1rrrrrr c1rrrrrr c1rrrrrr c1rrrrrr c1rrc1rr c1rrc1rr c1rrc1rr c1rrc1rr")
            pyxel.sound(2).set(s2, "n", mv, "f", 50)
        elif self.level in [3, 4]:
            s0 = ("e2g2b2e3 e2g2b2e3 c2e2g2c3 c2e2g2c3 a1c2e2a2 a1c2e2a2 b1d#2f#2b2 b1d#2f#2b2 e2g2b2g3 e2g2b2g3 c2e2g2e3 c2e2g2e3 a1c2e2c3 a1c2e2c3 b1d#2f#2d#3 b1d#2f#2d#3")
            pyxel.sound(0).set(s0, "p", mv, "v", 50)
            s1 = ("e1rrrrrr e1rrrrrr c1rrrrrr c1rrrrrr a0rrrrrr a0rrrrrr b0rrrrrr b0rrrrrr e1rre1rr e1rre1rr c1rrc1rr c1rrc1rr a0rra0rr a0rra0rr b0rrb0rr b0rrb0rr")
            pyxel.sound(1).set(s1, "s", mv, "n", 50)
            s2 = ("c1rrrrrr c1rrrrrr c1rrrrrr c1rrrrrr c1rrc1rr c1rrc1rr c1rrc1rr c1rrc1rr")
            pyxel.sound(2).set(s2, "n", mv, "f", 50)
        elif self.level in [5, 6]:
            s0 = ("f2a2c3f3 f2a2c3f3 d2f2a2d3 d2f2a2d3 a#1d2f2a#2 a#1d2f2a#2 c2e2g2c3 c2e2g2c3 f2a2c3a3 f2a2c3a3 e2g2c3e3 e2g2c3e3 d2f2a2d3 d2f2a2d3 c#2e2a2c#3 c#2e2a2c#3")
            pyxel.sound(0).set(s0, "p", mv, "v", 50)
            s1 = ("f1rrrrrr f1rrrrrr d1rrrrrr d1rrrrrr a#0rrrrrr a#0rrrrrr c1rrrrrr c1rrrrrr f1rrf1rr f1rrf1rr e1rre1rr e1rre1rr d1rrd1rr d1rrd1rr c#1rrc#1rr c#1rrc#1rr")
            pyxel.sound(1).set(s1, "t", mv, "n", 50)
            s2 = ("c1rrrrrr c1rrrrrr c1rrrrrr c1rrrrrr c1rrc1rr c1rrc1rr c1rrc1rr c1rrc1rr")
            pyxel.sound(2).set(s2, "n", mv, "f", 50)
        else: 
            s0 = ("d2 f2 a2 d3 a1 c#2 e2 a2 a#1 d2 f2 a#2 g1 a#1 d2 g2")
            pyxel.sound(0).set(s0, "s", mv, "v", 60)
            s1 = ("d1 r r r a0 r r r a#0 r r r g0 r r r")
            pyxel.sound(1).set(s1, "p", mv, "n", 60)
            pyxel.sound(2).set("c1 r r r r r r r", "n", mv, "f", 60)

        pyxel.play(0, 0, loop=True)
        pyxel.play(1, 1, loop=True)
        pyxel.play(2, 2, loop=True)

    def play_sfx(self, sound_id):
        # Plays a sound effect on Channel 3 (so it doesn't interrupt music)
        if self.sfx_vol > 0: pyxel.play(3, sound_id)

    # ==========================================
    # --- LEVEL RESET & INITIALIZATION ---
    # Called every time a new level starts to clear old enemies/bullets
    # ==========================================
    def reset_game(self):
        self.witch_darkness = False
        self.play_music()
        
        # Player Variables
        self.px, self.py = 40, FLOOR_Y - 32
        self.p_vx, self.p_vy = 0, 0
        self.p_hp = self.max_hp
        self.p_hp_disp = self.max_hp
        self.p_attack_cd = 0 
        self.p_slow_timer = 0
        self.spell_cooldown = 0
        self.max_cd = 60
        self.projectiles = []
        
        # Boss Variables & HP Scaling
        hp_scaling = [100, 300, 500, 800, 1200, 2000, 3500]
        if self.level == 0: 
            self.boss_hp = 9999; self.boss_max_hp = 9999
            self.tut_step = 0; self.tut_timer = 0
        else:
            self.boss_hp = hp_scaling[max(0, min(6, self.level - 1))]
            self.boss_max_hp = self.boss_hp
            
        self.boss_hp_disp = self.boss_max_hp
        self.boss_immune = False
        self.boss_timer = 0
        self.boss_vy = 0
        self.boss_action = 0 
        
        # Status Effects
        self.boss_on_fire = 0
        self.boss_freeze_stacks = 0
        self.boss_frozen_timer = 0
        self.p_poisoned = False
        self.flame_dmg_cd = 0 
        
        # Enemy & Trap Lists (Clearing the board)
        self.bombs = []; self.boss_bullets = []; self.squires = []
        self.squires_spawned = False
        self.geyser_warns = []; self.geysers = []; self.lava_timer = 0
        self.web_traps = []; self.tiny_spiders = []; self.latched_spiders = 0
        self.swarm_timer = 0; self.exploded = False
        self.soul_heads = []; self.skeletons = []; self.earth_spikes = []
        self.skeletons_killed = 0; self.skeletons_to_spawn = 20; self.witch_quakes = 0 
        
        self.particles.clear(); self.explosions.clear()
        self.flames.clear(); self.floating_texts.clear()
        self.boss_aim_angle = 0 
        
        # Boss Spawning
        self.b_target_x = 200 
        self.bx, self.by = 200, FLOOR_Y
        self.b_dir = -1
        self.platforms = []
        
        # Level-Specific setups (Platforms, Heights, etc.)
        if self.level == 0: self.by = FLOOR_Y
        elif self.level == 1: self.by = 80
        elif self.level == 2:
            self.swing_angle = 0 
            self.platforms = [[30, 160, 50, 150, 0, 150, 150, False], [176, 160, 50, 150, 0, 150, 150, False]]
        elif self.level == 3:
            self.platforms = [[40, 180, 40, 150, 0, 150, 150, False], [176, 180, 40, 150, 0, 150, 150, False], [108, 120, 40, 150, 0, 150, 150, False]]
        elif self.level == 6:
            self.platforms_spawned = False
            self.punch_timer = 0; self.combo_count = 0; self.punch_anim = 0
            self.platforms = [[40, 180, 40, 150, 0, 150, 150, False], [176, 180, 40, 150, 0, 150, 150, False], [108, 120, 40, 150, 0, 150, 150, False]]
        elif self.level == 7: 
            self.by = FLOOR_Y - 40
            self.credits_y = 256
        
    # ==========================================
    # --- VISUAL EFFECT SPAWNERS ---
    # Helper functions to quickly spawn effects anywhere in the game
    # ==========================================
    def spawn_particles(self, x, y, color, count=5, custom_vx=None, custom_vy=None, gravity=False):
        for _ in range(count):
            vx = custom_vx if custom_vx is not None else random.uniform(-4, 4)
            vy = custom_vy if custom_vy is not None else random.uniform(-4, 4)
            life = random.randint(10, 25)
            self.particles.append([x, y, vx, vy, life, color, gravity])

    def spawn_explosion(self, x, y, max_radius, color_core, color_edge):
        # [x, y, current_radius, max_radius, color_core, color_edge]
        self.explosions.append([x, y, 0, max_radius, color_core, color_edge])
        self.play_sfx(51) 
        
    def spawn_text(self, x, y, text, color):
        self.floating_texts.append([x, y, str(text), 30, color])
        
    def trigger_impact(self, shake_amt, flash=False):
        # Creates "Game Feel" by shaking the camera or flashing the screen white
        self.shake = max(self.shake, shake_amt)
        if flash: self.flash = 2

    # ==========================================
    # --- MASTER UPDATE LOOP ---
    # Called 30 times a second by Pyxel. Updates all logic, physics, and input.
    # ==========================================
    def update(self):
        prev_hp = self.p_hp
        prev_coins = self.coins

        # 1. Decay Timers (Camera shake, status effects, cooldowns)
        if self.shake > 0: self.shake -= 1
        if self.flash > 0: self.flash -= 1
        if self.p_attack_cd > 0: self.p_attack_cd -= 1
        if self.p_slow_timer > 0: self.p_slow_timer -= 1
        if getattr(self, 'flame_dmg_cd', 0) > 0: self.flame_dmg_cd -= 1

        if self.p_poisoned and pyxel.frame_count % 30 == 0:
            if not self.immortal: self.p_hp -= 2
            self.spawn_particles(self.px + 12, self.py + 16, 11, 2, gravity=False)
            self.spawn_text(self.px + 12, self.py - 5, "-2", 11)
            
        # 2. Update Weather and Ambience (Rain, Water Splashes, Mist)
        for r in self.rain:
            r[0] -= 1.5; r[1] += 5
            if r[1] > 256 or r[0] < 0:
                r[0] = random.randint(0, 350); r[1] = random.randint(-50, 0)
            elif r[1] > 160 and r[1] < 200 and random.random() < 0.05:
                self.water_splashes.append([r[0], r[1], 0])
                r[0] = random.randint(0, 350); r[1] = -10
                
        for l in self.leaves:
            l[0] -= 2 + math.sin(pyxel.frame_count * 0.05) * 1.5
            l[1] += math.cos(pyxel.frame_count * 0.1) * 1.0
            if l[0] < -10:
                l[0] = random.randint(260, 350); l[1] = random.randint(0, 160)

        for s in self.water_splashes[:]:
            s[2] += 0.1 
            if s[2] > 2.5: self.water_splashes.remove(s)

        for m in self.mist:
            m[0] -= m[2] + 0.5 
            if m[0] < -20: m[0] = SCREEN_WIDTH + random.randint(10, 50)

        # Update the Bird's swooping logic
        self.bird["x"] += 1.5
        if self.bird["x"] > 260:
            self.bird["x"] = -random.randint(50, 200); self.bird["y"] = random.randint(30, 80); self.bird["swooping"] = False
        if not self.bird["swooping"] and 50 < self.bird["x"] < 150 and random.random() < 0.01:
            self.bird["swooping"] = True; self.bird["target_y"] = 160
        elif not self.bird["swooping"]:
            self.bird["target_y"] = 50 + math.sin(pyxel.frame_count * 0.05) * 10
        self.bird["y"] += (self.bird["target_y"] - self.bird["y"]) * 0.05
        if self.bird["swooping"] and abs(self.bird["y"] - 160) < 5:
            self.bird["swooping"] = False; self.water_splashes.append([self.bird["x"], 160, 0])

        # Smoothly interpolate the display health bars
        self.p_hp_disp += (self.p_hp - self.p_hp_disp) * 0.1
        self.boss_hp_disp += (self.boss_hp - self.boss_hp_disp) * 0.1
        
        # 3. Update Visual Effects (Particles, Explosions, Text, Flames)
        for p in self.particles[:]:
            p[0] += p[2]; p[1] += p[3]
            if p[6]: p[3] += GRAVITY * 0.5 
            p[4] -= 1
            if p[4] <= 0: self.particles.remove(p)

        for e in self.explosions[:]:
            e[2] += 3.5 
            if e[2] >= e[3]: self.explosions.remove(e)
            
        for ft in self.floating_texts[:]:
            ft[1] -= 0.5; ft[3] -= 1
            if ft[3] <= 0: self.floating_texts.remove(ft)

        for f in self.flames[:]:
            f[0] += f[2]; f[1] += f[3]; f[4] -= 1
            current_radius = max(2, f[4] // 4)
            if abs(f[0] - (self.px + 12)) < 10 + current_radius and abs(f[1] - (self.py + 16)) < 14 + current_radius:
                if not self.immortal and getattr(self, 'flame_dmg_cd', 0) <= 0: 
                    self.p_hp -= 5 
                    self.flame_dmg_cd = 15 
                    self.trigger_impact(4)
            if f[4] <= 0: self.flames.remove(f)

        # 4. End Game Scenery Logic (Level 7 Defeated)
        if self.state == 7 or self.level == 7:
            self.wind_timer += 0.05
            for ff in self.fireflies:
                ff[0] += math.sin(ff[2] * 0.1) * 0.5 + ff[3]
                ff[1] += math.cos(ff[2] * 0.1) * 0.3
                ff[2] += 1
                if ff[0] < -5 or ff[0] > 261: ff[3] *= -1
                
            for b in self.butterflies:
                b["x"] += b["vx"] + math.sin(pyxel.frame_count * 0.1) * 0.3
                b["y"] += b["vy"] + math.cos(pyxel.frame_count * 0.15) * 0.2
                b["flap_timer"] += 1
                if b["x"] < 10 or b["x"] > 246: b["vx"] *= -1
                if b["y"] < 130 or b["y"] > 230: b["vy"] *= -1
                if random.random() < 0.01:
                    b["vx"] = random.uniform(-1.2, 1.2); b["vy"] = random.uniform(-0.6, 0.6)
                    
            for b in self.bird_flock:
                b["x"] += b["speed"]
                if b["x"] > 300: b["x"] = random.randint(-150, -50)
                
            # Autumn leaves logic updates (Drifting left)
            for leaf in self.autumn_leaves:
                leaf[0] -= leaf[2] * 1.5 + math.sin(leaf[1] * 0.05) * 0.5 
                leaf[1] += 0.8 
                if leaf[0] < -10 or leaf[1] > 260:
                    leaf[0] = random.randint(260, 300); leaf[1] = random.randint(-20, 200)

            # Blood dripping off the Warlock's robe
            self.blood_timer += 1
            if self.blood_timer > random.randint(45, 90):
                self.blood_timer = 0
                if random.random() < 0.7: 
                    drip_x = (128 - 12) + random.randint(4, 20); drip_y = (170 - 32) + random.randint(15, 30)
                    self.mage_blood_drips.append([drip_x, drip_y, 0, random.uniform(-0.1, 0.1)]) 
            
            for drip in self.mage_blood_drips[:]:
                drip[1] += drip[2]; drip[0] += drip[3]; drip[2] += GRAVITY * 0.4
                if drip[1] > 170: 
                    for _ in range(3):
                        self.particles.append([drip[0], drip[1]-1, random.uniform(-1, 1), random.uniform(-2, -0.5), 8, 8, True])
                    self.mage_blood_drips.remove(drip)
                    
            # Dove flying down to land on the hat
            if getattr(self, 'dove', None):
                if self.dove["state"] == "flying":
                    target_x, target_y = 128 + 12, 122 
                    ang = math.atan2(target_y - self.dove["y"], target_x - self.dove["x"])
                    self.dove["x"] += math.cos(ang) * 1.5; self.dove["y"] += math.sin(ang) * 1.5
                    if abs(self.dove["x"] - target_x) < 3 and abs(self.dove["y"] - target_y) < 3:
                        self.dove["x"], self.dove["y"] = target_x, target_y
                        self.dove["state"] = "landed"

        # 5. Route the Update Logic to the Correct State Handler
        if self.state == 0: self.update_menu()
        elif self.state == 1: self.update_upgrades()
        elif self.state == 2: self.update_dev()
        elif self.state == 6: self.update_settings()
        elif self.state == 3: self.update_play()
        elif self.state == 7: self.update_credits()
        elif self.state == 4: # Game Over
            if pyxel.btnp(pyxel.KEY_SPACE): self.state = 0
        elif self.state == 5: # Victory Screen
            if pyxel.btnp(pyxel.KEY_SPACE):
                if self.level == 7:
                    self.state = 7; self.credits_y = 256; self.play_music()
                else:
                    self.level += 1; self.reset_game(); self.state = 3

        # Feedback sounds
        if self.p_hp < prev_hp: self.play_sfx(54) 
        if self.coins > prev_coins: self.play_sfx(53) 

    # --- MENU STATE HANDLERS ---
    def update_menu(self):
        if pyxel.btnp(pyxel.KEY_A): self.level = 1; self.reset_game(); self.state = 3
        if pyxel.btnp(pyxel.KEY_B): self.state = 1
        if pyxel.btnp(pyxel.KEY_C): self.dev_unlocked = True; self.state = 2
        if pyxel.btnp(pyxel.KEY_D): self.state = 6
        if pyxel.btnp(pyxel.KEY_E): self.level = 0; self.reset_game(); self.state = 3

    def update_settings(self):
        if pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.KEY_W):
            self.music_vol = min(7, self.music_vol + 1); self.init_audio(); self.play_music()
        if pyxel.btnp(pyxel.KEY_DOWN) or pyxel.btnp(pyxel.KEY_S):
            self.music_vol = max(0, self.music_vol - 1); self.init_audio(); self.play_music()
        if pyxel.btnp(pyxel.KEY_RIGHT) or pyxel.btnp(pyxel.KEY_D):
            self.sfx_vol = min(7, self.sfx_vol + 1); self.init_audio(); self.play_sfx(50)
        if pyxel.btnp(pyxel.KEY_LEFT) or pyxel.btnp(pyxel.KEY_A):
            self.sfx_vol = max(0, self.sfx_vol - 1); self.init_audio(); self.play_sfx(50)
        if pyxel.btnp(pyxel.KEY_BACKSPACE): self.state = 0

    def update_upgrades(self):
        dmg_cost = self.damage_lv * 50
        spd_cost = self.proj_speed_lv * 30
        hp_cost = self.hp_lv * 40
        
        if pyxel.btnp(pyxel.KEY_1) and self.coins >= dmg_cost:
            self.coins -= dmg_cost; self.damage_lv += 1; self.play_sfx(53)
        if pyxel.btnp(pyxel.KEY_2) and self.coins >= spd_cost:
            self.coins -= spd_cost; self.proj_speed_lv += 1; self.play_sfx(53)
        if pyxel.btnp(pyxel.KEY_3) and self.coins >= hp_cost:
            self.coins -= hp_cost; self.hp_lv += 1; self.max_hp += 20; self.p_hp += 20; self.play_sfx(53)
            
        def buy_or_equip(spell_name, cost):
            if spell_name in self.unlocked_spells:
                self.active_spell = spell_name; self.play_sfx(53)
            elif self.coins >= cost:
                self.coins -= cost; self.unlocked_spells.append(spell_name); self.active_spell = spell_name; self.play_sfx(53)

        if pyxel.btnp(pyxel.KEY_4): buy_or_equip("Teleport", 100)
        if pyxel.btnp(pyxel.KEY_5): buy_or_equip("Fireball", 150)
        if pyxel.btnp(pyxel.KEY_6): buy_or_equip("Freeze", 150)
        if pyxel.btnp(pyxel.KEY_7): buy_or_equip("Heal", 200)
        if pyxel.btnp(pyxel.KEY_BACKSPACE): self.state = 0

    def update_dev(self):
        mx, my = pyxel.mouse_x, pyxel.mouse_y
        if pyxel.btnp(pyxel.MOUSE_BUTTON_LEFT):
            if 20 <= mx <= 120 and 160 <= my <= 175: self.slider_drag = True
            spells = ["Teleport", "Fireball", "Freeze", "Heal"]
            for i, sp in enumerate(spells):
                if 20 + i*45 <= mx <= 60 + i*45 and 190 <= my <= 205:
                    if sp not in self.unlocked_spells: self.unlocked_spells.append(sp)
                    self.active_spell = sp; self.play_sfx(53)
                
        if pyxel.btn(pyxel.MOUSE_BUTTON_LEFT) and self.slider_drag:
            self.dev_dmg_slider = max(0.0, min(1.0, (mx - 20) / 100.0))
        if pyxel.btnr(pyxel.MOUSE_BUTTON_LEFT): self.slider_drag = False

        # Warp Console (Jump to any level)
        for i in range(1, 8):
            if pyxel.btnp(getattr(pyxel, f"KEY_{i}")):
                self.level = i; self.immortal = True; self.reset_game(); self.state = 3

        if pyxel.btnp(pyxel.KEY_I): self.immortal = not self.immortal
        if pyxel.btnp(pyxel.KEY_BACKSPACE): self.state = 0
        
    def update_credits(self):
        self.credits_y -= 0.5 # Slowly scroll credits up
        limit = -((len(self.credits_list) if self.credits_list else 100) * 40 + 200)
        if self.credits_y < limit:
            if pyxel.btnp(pyxel.KEY_SPACE): self.state = 0

    # ==========================================
    # --- GAMEPLAY LOGIC (THE HEART OF THE GAME) ---
    # Handles Player Movement, Spells, Collision, and Boss AI
    # ==========================================
    def update_play(self):
        # 1. Calculate Player Speed & Status Debuffs
        current_speed = PLAYER_SPEED
        if getattr(self, 'shake', 0) > 0 and self.level == 7: current_speed *= 0.5
        if self.p_slow_timer > 0: current_speed = 1.5
        if getattr(self, 'witch_darkness', False): current_speed *= 0.5
            
        # Check if standing in Web Traps
        in_web = False
        for w in self.web_traps[:]:
            w[2] -= 1
            if w[2] <= 0: self.web_traps.remove(w)
            elif abs((self.px + 12) - w[0]) < 35 and abs((self.py + 32) - w[1]) < 15:
                in_web = True
        
        if in_web: 
            self.p_slow_timer = 240 
            if not self.immortal and pyxel.frame_count % 30 == 0:
                self.p_hp -= 3; self.trigger_impact(2)
                
        if self.latched_spiders > 0: current_speed = max(0.5, current_speed - (self.latched_spiders * 0.3))

        # 2. Player Input (Movement)
        if pyxel.btn(pyxel.KEY_A): self.px -= current_speed
        if pyxel.btn(pyxel.KEY_D): self.px += current_speed
        
        # 3. Physics: Gravity Application
        self.p_vy += GRAVITY
        self.py += self.p_vy
        on_ground = False

        # 4. Platform Collision Logic
        for plat in getattr(self, 'platforms', []):
            while len(plat) < 8: plat.append(False) 
            
            px_center = self.px + 12
            if px_center > plat[0] and px_center < (plat[0] + plat[2]):
                plat[7] = True  # Player is directly above/on the platform
            else:
                plat[7] = False
                
            if plat[3] > 0: # If Platform is "Alive"
                # Check if falling AND player's feet hit the top of the platform
                if self.p_vy >= 0 and self.py + 32 >= plat[1] and self.py + 32 - self.p_vy <= plat[1] + 10:
                    if px_center > plat[0] and px_center < plat[0] + plat[2]:
                        self.py = plat[1] - 32; self.p_vy = 0; on_ground = True
                        
                if plat[7]: # If standing on it, reduce its "health" (5-second timer)
                    plat[3] -= 1 
                    if plat[3] % 30 == 0 or plat[3] < 90: 
                        self.spawn_particles(plat[0] + random.randint(0, plat[2]), plat[1] + 4, 13, 1, custom_vy=0.5)
            else: # Platform is Broken, start respawn timer
                plat[4] -= 1 
                if plat[4] <= 0:
                    plat[3] = plat[5]; plat[4] = plat[6] 
                    plat[7] = False 
                    self.spawn_particles(plat[0] + plat[2]//2, plat[1] + 4, 10, 15)

        # Main Floor Collision
        if self.py > FLOOR_Y - 32: 
            self.py = FLOOR_Y - 32; self.p_vy = 0; on_ground = True

        # Jump Logic
        if pyxel.btnp(pyxel.KEY_SPACE) and on_ground:
            jump_pwr = JUMP_STRENGTH
            if getattr(self, 'witch_darkness', False): jump_pwr = -6 
            self.p_vy = jump_pwr
            self.play_sfx(52) 
            self.spawn_particles(self.px + 12, self.py + 32, 7, 8, gravity=True)

        # Tutorial Phase (Level 0) Sequence
        if self.level == 0:
            if self.tut_step == 0:
                if pyxel.btn(pyxel.KEY_A) or pyxel.btn(pyxel.KEY_D): self.tut_timer += 1
                if self.tut_timer > 60: self.tut_step = 1; self.tut_timer = 0
            elif self.tut_step == 1:
                if self.boss_hp < self.boss_max_hp: self.tut_step = 2
            elif self.tut_step == 2:
                if "Fireball" not in self.unlocked_spells:
                    self.unlocked_spells.append("Fireball")
                    self.active_spell = "Fireball"
                if pyxel.btnp(pyxel.KEY_Q): self.tut_step = 3; self.tut_timer = 0
            elif self.tut_step == 3:
                self.tut_timer += 1
                if self.tut_timer > 120:
                    self.level = 1; self.state = 0; return

        # 5. Combat: Player Basic Attack (Left Click)
        if pyxel.btn(pyxel.MOUSE_BUTTON_LEFT) and (self.p_attack_cd <= 0 or self.dev_unlocked):
            self.p_attack_cd = 12 if getattr(self, 'witch_darkness', False) else 6 
            self.shake = max(self.shake, 2) 
            # Calculate trajectory angle from player center to mouse cursor
            dx = pyxel.mouse_x - (self.px + 12); dy = pyxel.mouse_y - (self.py + 16)
            angle = math.atan2(dy, dx)
            speed = 6 + (self.proj_speed_lv * 1.5)
            # Create projectile: [x, y, vx, vy, type]
            self.projectiles.append([self.px + 12, self.py + 16, math.cos(angle) * speed, math.sin(angle) * speed, "basic"])
            self.spawn_particles(self.px + 12, self.py + 16, 11, 4, -math.cos(angle)*3, -math.sin(angle)*3)
            self.play_sfx(50) 
            # Clear spiders if any are latched onto you
            if self.latched_spiders > 0:
                self.spawn_particles(self.px + 12, self.py + 16, 3, self.latched_spiders*4)
                self.spawn_text(self.px, self.py - 20, "CLEARED", 3)
                self.latched_spiders = 0

        # 6. Combat: Spells (Q Key)
        if pyxel.btnp(pyxel.KEY_Q) and self.spell_cooldown <= 0 and self.active_spell:
            if self.active_spell == "Teleport":
                self.spawn_explosion(self.px + 12, self.py + 16, 25, 12, 1)
                self.px += 60 if pyxel.btn(pyxel.KEY_D) else -60
                self.spawn_explosion(self.px + 12, self.py + 16, 25, 7, 12)
                self.trigger_impact(4); self.spell_cooldown = 60; self.max_cd = 60; self.play_sfx(50)
                
            elif self.active_spell == "Fireball":
                dx = pyxel.mouse_x - (self.px + 12); dy = pyxel.mouse_y - (self.py + 16)
                angle = math.atan2(dy, dx)
                self.projectiles.append([self.px + 12, self.py + 16, math.cos(angle) * 8, math.sin(angle) * 8, "fireball"])
                self.trigger_impact(4); self.spell_cooldown = 120; self.max_cd = 120; self.play_sfx(50)
                
            elif self.active_spell == "Freeze":
                dx = pyxel.mouse_x - (self.px + 12); dy = pyxel.mouse_y - (self.py + 16)
                angle = math.atan2(dy, dx)
                self.projectiles.append([self.px + 12, self.py + 16, math.cos(angle) * 10, math.sin(angle) * 10, "freeze"])
                self.spell_cooldown = 90; self.max_cd = 90; self.play_sfx(50)
                
            elif self.active_spell == "Heal":
                heal_amt = int(self.max_hp * 0.35) 
                self.p_hp = min(self.max_hp, self.p_hp + heal_amt)
                self.spawn_particles(self.px + 12, self.py + 16, 11, 20, gravity=False)
                self.spawn_text(self.px + 12, self.py - 10, f"+{heal_amt}", 11)
                self.trigger_impact(2, flash=True)
                self.spell_cooldown = 270; self.max_cd = 270 
                self.play_sfx(53)
                
        if self.spell_cooldown > 0: self.spell_cooldown -= 1

        # 7. Player Projectile Collisions
        mult = 1.0 + (self.dev_dmg_slider * 99) if self.dev_unlocked else 1.0
        base_dmg = int((2 * self.damage_lv) * mult)
        
        # Fire DoT (Damage over Time) tick processing
        if self.boss_on_fire > 0:
            self.boss_on_fire -= 1
            if pyxel.frame_count % 15 == 0:
                tick_dmg = int((self.damage_lv * 2) * mult)
                self.boss_hp -= tick_dmg
                self.spawn_text(self.bx, self.by - 30, str(tick_dmg), 8)
                self.spawn_particles(self.bx, self.by - 20, 9, 3, gravity=True)
        
        for p in self.projectiles[:]:
            p[0] += p[2]; p[1] += p[3]
            
            # --- Fireball Special Logic ---
            if len(p) > 4 and p[4] == "fireball":
                # HTML FILTER FIX: Spaces inside '< bw ' and '< bh' prevent web editors from deleting code!
                bw = 20 if self.level in [1, 6, 0] else (40 if self.level == 4 else (16 if self.level == 7 else 24))
                bh = 20 if self.level == 1 else (40 if self.level in [4, 6, 0] else (40 if self.level == 7 else 45))
                by_off = self.by if self.level in [1, 0] else self.by - 25
                
                hit = False
                if p[1] >= FLOOR_Y: 
                    hit = True
                elif not self.boss_immune:
                    dist_x = abs(p[0] - self.bx)
                    dist_y = abs(p[1] - by_off)
                    if dist_x < bw and dist_y < bh: 
                        hit = True
                        
                if not hit:
                    for s in self.squires:
                        if abs(p[0] - s[0]) < 16 and abs(p[1] - s[1]) < 16: 
                            hit = True; break
                    for sk in self.skeletons:
                        if abs(p[0] - sk[0]) < 16 and abs(p[1] - sk[1]) < 20: 
                            hit = True; break
                
                if hit:
                    actual_dmg = base_dmg * 2
                    self.spawn_explosion(p[0], p[1], 45, 9, 8) 
                    self.spawn_particles(p[0], p[1], 10, 20, custom_vx=random.uniform(-5,5), custom_vy=random.uniform(-5,5))
                    self.spawn_particles(p[0], p[1], 9, 15, gravity=True)
                    self.trigger_impact(12, flash=True); self.play_sfx(63) 
                    
                    # Fireball AoE hits Boss
                    if not self.boss_immune and abs(p[0] - self.bx) < 60 and abs(p[1] - by_off) < 60:
                        self.boss_hp -= actual_dmg; self.boss_on_fire = 150
                        self.spawn_text(self.bx, by_off - 20, actual_dmg, 8)
                                 
                    # Fireball AoE hits enemies
                    for s in self.squires[:]:
                        if abs(p[0] - s[0]) < 50 and abs(p[1] - s[1]) < 50:
                            if len(s) > 2:
                                if s[3] == 2 and s[6] > 0:
                                    s[6] -= 5; self.play_sfx(60); self.spawn_text(s[0], s[1] - 15, "BLOCKED", 6)
                                else:
                                    s[2] -= actual_dmg
                                    if s[2] <= 0:
                                        if s in self.squires: self.squires.remove(s)
                                        if self.level != 0: self.coins += 5
                                        self.spawn_explosion(s[0], s[1], 25, 9, 10)
                            else:
                                if s in self.squires: self.squires.remove(s)
                                if self.level != 0: self.coins += 2
                                self.spawn_explosion(s[0], s[1], 25, 9, 10)
                                
                    for sk in self.skeletons[:]:
                        if abs(p[0] - sk[0]) < 50 and abs(p[1] - sk[1]) < 50:
                            sk[2] -= actual_dmg
                            if sk[2] <= 0:
                                if sk in self.skeletons: self.skeletons.remove(sk)
                                self.skeletons_killed += 1
                                self.spawn_explosion(sk[0], sk[1], 25, 7, 13)
                                
                    for ts in self.tiny_spiders[:]:
                        if abs(p[0] - ts[0]) < 50 and abs(p[1] - ts[1]) < 50:
                            if ts in self.tiny_spiders: self.tiny_spiders.remove(ts)
                            self.spawn_particles(ts[0], ts[1], 11, 15, gravity=True)
                            self.spawn_particles(ts[0], ts[1], 3, 5, gravity=True)
                            
                    if p in self.projectiles: self.projectiles.remove(p)
                continue # Skip the rest of the loop for fireballs

            # --- Standard & Freeze Projectile Hitboxes ---
            # Set dynamic hitboxes based on the current boss size
            bw = 20 if self.level in [1, 6, 0] else (40 if self.level == 4 else (16 if self.level == 7 else 24))
            bh = 20 if self.level == 1 else (40 if self.level in [4, 6, 0] else (40 if self.level == 7 else 45))
            by_off = self.by if self.level in [1, 0] else self.by - 25
            
            # Basic Hit check vs Boss
            if not self.boss_immune and abs(p[0] - self.bx) < bw and abs(p[1] - by_off) < bh:
                actual_dmg = base_dmg
                if p[4] == "freeze":
                    actual_dmg = int(base_dmg * 0.5)
                    self.boss_freeze_stacks += 1
                    if self.boss_freeze_stacks >= 3:
                        self.boss_frozen_timer = 150 
                        self.boss_freeze_stacks = 0
                        self.spawn_explosion(p[0], p[1], 40, 12, 7) 
                        self.trigger_impact(8, flash=True)
                    else:
                        self.spawn_particles(p[0], p[1], 12, 10)
                
                self.boss_hp -= actual_dmg
                if self.level != 0: self.coins += 1
                self.play_sfx(55) 
                self.spawn_particles(p[0], p[1], 8, 15, gravity=True) 
                self.spawn_text(p[0], p[1] - 20, actual_dmg, 10 if mult > 2 else 7)
                self.trigger_impact(4)
                if p in self.projectiles: self.projectiles.remove(p)
                continue
            
            # Hit check vs Tiny Spiders (Level 5)
            hit_tiny = False
            for ts in self.tiny_spiders[:]:
                if abs(p[0] - ts[0]) < 12 and abs(p[1] - ts[1]) < 12: 
                    if ts in self.tiny_spiders: self.tiny_spiders.remove(ts)
                    self.play_sfx(55); self.spawn_particles(ts[0], ts[1], 11, 15, gravity=True) 
                    self.spawn_particles(ts[0], ts[1], 3, 5, gravity=True); self.shake = max(self.shake, 1)
                    hit_tiny = True
            if hit_tiny: continue 

            # Hit check vs Skeletons (Level 7)
            for sk in self.skeletons[:]:
                if abs(p[0] - sk[0]) < 12 and abs(p[1] - sk[1]) < 20:
                    sk[2] -= base_dmg; self.spawn_particles(p[0], p[1], 7, 5); self.play_sfx(55)
                    if sk[2] <= 0:
                        if sk in self.skeletons: self.skeletons.remove(sk)
                        self.skeletons_killed += 1
                        self.spawn_explosion(sk[0], sk[1], 25, 7, 13)
                    if p in self.projectiles: self.projectiles.remove(p)
                    break

            # Hit check vs Squires (Level 1 & 4)
            for s in self.squires[:]:
                sx, sy = s[0], s[1]
                hit = False
                if len(s) > 2: # Full squires
                    if abs(p[0] - sx) < 12 and abs(p[1] - sy) < 16:
                        # Block mechanic logic
                        if s[3] == 2 and s[6] > 0: 
                            if (p[2] > 0 and s[4] == -1) or (p[2] < 0 and s[4] == 1):
                                s[6] -= 1; self.play_sfx(60); self.spawn_particles(p[0], p[1], 7, 5)
                                self.spawn_text(p[0], p[1] - 10, "BLOCKED", 6)
                                if p in self.projectiles: self.projectiles.remove(p)
                                break
                        s[2] -= base_dmg; hit = True; self.play_sfx(55)
                        if p in self.projectiles: self.projectiles.remove(p)
                        if s[2] <= 0:
                            if s in self.squires: self.squires.remove(s)
                            if self.level != 0: self.coins += 5
                            self.spawn_explosion(sx, sy, 25, 9, 10); self.trigger_impact(8, flash=True)
                else: # Mini squires
                    if abs(p[0] - sx) < 12 and abs(p[1] - sy) < 12:
                        if s in self.squires: self.squires.remove(s)
                        if self.level != 0: self.coins += 2
                        self.spawn_explosion(sx, sy, 25, 9, 10); self.trigger_impact(8, flash=True)
                        if p in self.projectiles: self.projectiles.remove(p)
                if hit: break
            
            # Boundary removal
            if p[0] < 0 or p[0] > SCREEN_WIDTH or p[1] < 0 or p[1] > SCREEN_HEIGHT:
                if p in self.projectiles: self.projectiles.remove(p)

        # 8. Boss AI & Mechanics
        
        # Geyser Trap Logic (Shared across Bosses)
        for w in self.geyser_warns[:]:
            w[1] -= 1
            if w[1] <= 0:
                self.geysers.append([w[0], 30]); self.geyser_warns.remove(w)
                self.play_sfx(62); self.trigger_impact(10)
        for g in self.geysers[:]:
            g[1] -= 1
            if abs((self.px + 12) - g[0]) < 20:
                if not self.immortal and pyxel.frame_count % 5 == 0:
                    self.p_hp -= 4; self.trigger_impact(4)
            if g[1] <= 0: self.geysers.remove(g)

        # Stop AI if Boss is Frozen
        if self.boss_frozen_timer > 0:
            self.boss_frozen_timer -= 1
            if pyxel.frame_count % 5 == 0:
                self.spawn_particles(self.bx, self.by - 20, 12, 2, gravity=True)
                
        # Main AI State Machine per Level
        elif self.level != 0:
            hp_pct = self.boss_hp / self.boss_max_hp
            
            # --- LEVEL 1: The First Boss ---
            if self.level == 1:
                self.boss_timer += 1
                if pyxel.frame_count % 30 == 0: self.b_target_x = random.randint(40, 210)
                speed = 1.0
                if getattr(self, 'b_target_x', 200):
                    if self.bx < self.b_target_x - 5: self.bx += speed; self.b_dir = 1
                    elif self.bx > self.b_target_x + 5: self.bx -= speed; self.b_dir = -1
                
                if hp_pct < 0.3 and not self.squires_spawned:
                    self.boss_immune = True; self.squires = [[40, 80], [128, 80], [210, 80]]
                    self.squires_spawned = True; self.trigger_impact(20, flash=True) 
                if len(self.squires) == 0: self.boss_immune = False
                
                attack_rate = max(10, 40 - (self.level * 5))
                if self.boss_timer % attack_rate == 0:
                    if hp_pct > 0.6: self.bombs.append([self.bx, self.by + 10, 0])
                    elif hp_pct > 0.3:
                        for angle in [-0.5, 0, 0.5]: self.bombs.append([self.bx, self.by + 10, angle])
                
                for b in self.bombs[:]:
                    b[1] += 3; b[0] += b[2]*3
                    self.spawn_particles(b[0], b[1], 7, 2) 
                    if abs(b[0] - (self.px + 12)) < 12 and abs(b[1] - (self.py + 16)) < 12:
                        if not self.immortal: self.p_hp -= 10
                        self.spawn_explosion(b[0], b[1], 35, 8, 9); self.bombs.remove(b)
                        self.trigger_impact(12, flash=True)
                    elif b[1] > FLOOR_Y:
                        self.spawn_explosion(b[0], b[1], 25, 7, 8); self.bombs.remove(b)
                        self.trigger_impact(4)
                        
            # --- LEVEL 2: The Bruiser ---
            elif self.level == 2:
                rage = hp_pct < 0.5
                action_speed = 1.5 if rage else 1.0
                
                if self.boss_action == 0: # Walking/Chasing
                    speed = 2.0 if rage else 1.0
                    if self.bx < self.px - 40: self.bx += speed; self.b_dir = 1
                    elif self.bx > self.px + 40: self.bx -= speed; self.b_dir = -1
                    
                    self.boss_timer += action_speed
                    if self.boss_timer > 50:
                        self.boss_timer = 0
                        self.swing_angle = math.pi if self.b_dir == 1 else 0 
                        if rage and random.random() < 0.5: self.boss_action = 3 
                        else: self.boss_action = 1 
                        
                elif self.boss_action == 1: # Windup Attack
                    self.boss_timer += action_speed
                    self.swing_angle += (0.05 * action_speed * self.b_dir)
                    if self.boss_timer > 25:
                        self.boss_timer = 0; self.boss_action = 2; self.play_sfx(57) 
                        
                elif self.boss_action == 2: # Heavy Swing
                    self.boss_timer += action_speed
                    arc = (math.pi * 1.2) * (self.boss_timer / 15)
                    self.swing_angle = (math.pi if self.b_dir == -1 else 0) + (arc * self.b_dir)
                    
                    if self.boss_timer > 5 and self.boss_timer < 10: 
                        hit_dist = abs((self.px + 12) - self.bx)
                        if hit_dist < 80 and self.py > FLOOR_Y - 50:
                            if not self.immortal: self.p_hp -= (30 if rage else 20)
                            self.trigger_impact(20, flash=True)
                            self.spawn_explosion(self.px + 12, self.py + 16, 35, 8, 2)
                            self.boss_timer = 15 
                            
                    if self.boss_timer > 15: self.boss_timer = 0; self.boss_action = 0
                        
                elif self.boss_action == 3: # Breath Windup
                    self.boss_timer += action_speed
                    if pyxel.frame_count % 2 == 0: 
                        self.spawn_particles(self.bx + (12*self.b_dir), self.by - 40, 9, 5, gravity=True)
                    if self.boss_timer > 30:
                        self.boss_timer = 0; self.boss_action = 4; self.trigger_impact(8)
                        
                elif self.boss_action == 4: # Breathing Fire
                    self.boss_timer += action_speed
                    self.shake = max(self.shake, 6) 
                    breath_x, breath_y = self.bx, self.by - 46 
                    target_x, target_y = self.px + 12, self.py + 16
                    aim_angle = math.atan2(target_y - breath_y, target_x - breath_x)
                    
                    for _ in range(25 if rage else 15):
                        vx = math.cos(aim_angle + random.uniform(-0.35, 0.35)) * random.uniform(5, 12)
                        vy = math.sin(aim_angle + random.uniform(-0.35, 0.35)) * random.uniform(5, 12)
                        self.flames.append([breath_x, breath_y, vx, vy, random.randint(30, 55)])
                                
                    if self.boss_timer > (50 if rage else 70):
                        self.boss_timer = 0; self.boss_action = 0

            # --- LEVEL 3: The Gunner ---
            elif self.level == 3:
                rage = hp_pct < 0.4
                target_x, target_y = self.px + 12, self.py + 16
                gun_x, gun_y = self.bx, self.by - 32
                self.boss_aim_angle = math.atan2(target_y - gun_y, target_x - gun_x)
                self.b_dir = 1 if self.px > self.bx else -1

                if self.boss_action == 0: 
                    speed = 1.2
                    if self.bx < self.px - 60: self.bx += speed
                    elif self.bx > self.px + 60: self.bx -= speed
                    self.boss_timer += 1
                    if self.boss_timer > 40:
                        self.boss_timer = 0; self.boss_action = 2 if rage else 1
                        
                elif self.boss_action == 1: # Shotgun attack
                    self.boss_timer += 1
                    if self.boss_timer == 10:
                        self.trigger_impact(12, flash=True)
                        self.spawn_explosion(gun_x + math.cos(self.boss_aim_angle)*20, gun_y + math.sin(self.boss_aim_angle)*20, 25, 10, 9)
                        for a_off in [-0.3, -0.15, 0, 0.15, 0.3]:
                            aim = self.boss_aim_angle + a_off
                            self.boss_bullets.append([gun_x, gun_y, math.cos(aim)*8, math.sin(aim)*8, "shotgun"])
                    if self.boss_timer > 40: self.boss_timer = 0; self.boss_action = 0
                        
                elif self.boss_action == 2: # Minigun attack
                    self.boss_timer += 1
                    if self.boss_timer % 3 == 0:
                        self.shake = max(self.shake, 4)
                        a = self.boss_aim_angle + random.uniform(-0.1, 0.1)
                        self.boss_bullets.append([gun_x, gun_y, math.cos(a)*6, math.sin(a)*6, "mini"])
                    if self.boss_timer > 60: self.boss_timer = 0; self.boss_action = 0

                # Boss Bullet logic
                for b in self.boss_bullets[:]:
                    b[0] += b[2]; b[1] += b[3]
                    if abs(b[0] - (self.px + 12)) < 10 and abs(b[1] - (self.py + 16)) < 10:
                        if not self.immortal: self.p_hp -= (15 if b[4] == "shotgun" else 4)
                        self.trigger_impact(8); self.spawn_explosion(self.px + 12, self.py + 16, 20, 8, 2)
                        if b in self.boss_bullets: self.boss_bullets.remove(b)
                        continue

                    # Bullets destroying platforms
                    for plat in getattr(self, 'platforms', []):
                        if plat[3] > 0: 
                            if plat[0] <= b[0] <= plat[0] + plat[2] and plat[1] <= b[1] <= plat[1] + 10:
                                plat[3] -= (5 if b[4] == "shotgun" else 1) 
                                self.spawn_particles(b[0], b[1], 13, 3, gravity=True)
                                if b in self.boss_bullets: self.boss_bullets.remove(b)
                                break
                                
                    if b[0] < 0 or b[0] > SCREEN_WIDTH or b[1] < 0 or b[1] > SCREEN_HEIGHT:
                        if b in self.boss_bullets: self.boss_bullets.remove(b)

            # --- LEVEL 4: The Slime / Burger Boss ---
            elif self.level == 4:
                self.boss_vy += GRAVITY
                self.by += self.boss_vy
                if self.by > FLOOR_Y: self.by = FLOOR_Y; self.boss_vy = 0
                    
                if hp_pct > 0.8: # Phase 1: Bounce around
                    self.boss_timer += 1
                    if self.boss_timer > 60:
                        self.b_dir = 1 if self.bx < 128 else -1
                        self.boss_timer = 0
                    self.bx += 4.0 * self.b_dir
                    if self.bx < 20: self.b_dir = 1
                    if self.bx > 236: self.b_dir = -1
                    if pyxel.frame_count % 30 == 0: self.play_sfx(62) 
                    if random.random() < 0.02 and self.by == FLOOR_Y: self.boss_vy = -8 # Random jumps
                    if abs(self.bx - (self.px + 12)) < 40 and abs((self.by - 20) - (self.py + 16)) < 30:
                        if not self.immortal and pyxel.frame_count % 10 == 0:
                            self.p_hp -= 15; self.trigger_impact(6)
                
                elif hp_pct > 0.4 and not self.squires_spawned: # Phase 2 transition
                    self.boss_immune = True
                    self.squires_spawned = True
                    self.boss_action = 1 
                    for _ in range(9):
                        sx = random.randint(20, 236)
                        self.squires.append([sx, FLOOR_Y, 20, random.randint(0, 2), 1 if sx < 128 else -1, 0, 15]) 
                    self.trigger_impact(20, flash=True); self.play_sfx(56) 
                    
                elif hp_pct > 0.4 and self.squires_spawned: # Phase 2 Logic
                    if self.bx < 120: self.bx += 1
                    elif self.bx > 136: self.bx -= 1
                    
                    for s in self.squires:
                        s[4] = 1 if s[0] < self.px else -1
                        if s[3] == 0: 
                            s[0] += 1.5 * s[4]
                            if abs(s[0] - (self.px + 12)) < 16 and abs(s[1] - (self.py + 32)) < 16:
                                if not self.immortal and pyxel.frame_count % 15 == 0:
                                    self.p_hp -= 10; self.trigger_impact(4)
                        elif s[3] == 1: 
                            s[5] += 1
                            if s[5] > 90:
                                s[5] = 0
                                angle = math.atan2(self.py + 16 - s[1], self.px + 12 - s[0])
                                self.boss_bullets.append([s[0], s[1] - 16, math.cos(angle)*5, math.sin(angle)*5, "mini"])
                        elif s[3] == 2: 
                            s[0] += 0.5 * s[4]
                            if abs(s[0] - (self.px + 12)) < 16 and abs(s[1] - (self.py + 32)) < 16:
                                if not self.immortal and pyxel.frame_count % 30 == 0:
                                    self.p_hp -= 5; self.trigger_impact(2)
                                    
                    if len(self.squires) == 0: 
                        self.boss_immune = False; self.boss_action = 2 
                        
                    if self.boss_action == 2: 
                        self.boss_timer += 1
                        if self.boss_timer > 45:
                            self.boss_timer = 0
                            angle = math.atan2(self.py + 16 - (self.by - 20), self.px + 12 - self.bx)
                            self.boss_bullets.append([self.bx, self.by - 20, math.cos(angle)*6, math.sin(angle)*6, "burger"])
                
                else: # Phase 3: The Floor is Lava (Geyser Survival)
                    # Exactly 11 seconds = 330 frames
                    if self.lava_timer < 330: 
                        self.boss_immune = True
                        self.lava_timer += 1
                        if self.lava_timer % 20 == 0: 
                            self.geyser_warns.append([self.px + random.randint(-60, 60), 40]) 
                    else: 
                        self.boss_immune = False; self.boss_action = 3; self.boss_timer += 1
                        if self.boss_timer > 20: 
                            self.boss_timer = 0
                            angle = math.atan2(self.py + 16 - (self.by - 20), self.px + 12 - self.bx)
                            self.boss_bullets.append([self.bx, self.by - 20, math.cos(angle)*8, math.sin(angle)*8, "burger"])

                # Burger bullet physics (bouncing)
                for b in self.boss_bullets[:]:
                    b[0] += b[2]; b[1] += b[3]
                    if b[4] == "burger":
                        b[3] += GRAVITY * 0.5 
                        if b[1] > FLOOR_Y: b[1] = FLOOR_Y; b[3] = -abs(b[3]) * 0.8
                    if abs(b[0] - (self.px + 12)) < 12 and abs(b[1] - (self.py + 16)) < 12:
                        if not self.immortal: self.p_hp -= 15
                        self.trigger_impact(8); self.spawn_explosion(self.px + 12, self.py + 16, 20, 8, 2)
                        if b in self.boss_bullets: self.boss_bullets.remove(b)
                        continue
                    if b[0] < -50 or b[0] > SCREEN_WIDTH + 50 or b[1] < -50 or b[1] > SCREEN_HEIGHT:
                        if b in self.boss_bullets: self.boss_bullets.remove(b)

            # --- LEVEL 5: The Spider ---
            elif self.level == 5:
                if hp_pct <= 0.3 and not self.exploded:
                    self.exploded = True; self.boss_action = 4 
                    self.boss_hp = self.boss_max_hp * 0.1 
                    self.spawn_explosion(self.bx, self.by - 20, 100, 7, 13)
                    self.trigger_impact(25, flash=True); self.play_sfx(56)
                    if abs((self.px + 12) - self.bx) < 100 and abs((self.py + 16) - (self.by - 20)) < 100:
                        if not self.immortal: self.p_hp -= 40 
                
                elif hp_pct <= 0.6 and hp_pct > 0.4 and self.boss_action == 0:
                    self.boss_action = 1 
                    self.boss_immune = True
                    self.swarm_timer = 0
                    self.tiny_spiders.clear()
                    self.latched_spiders = 0
                    
                elif self.boss_action == 1 and self.swarm_timer > 1200:
                    self.boss_action = 2 
                    self.boss_immune = False; self.by = -50; self.bx = 128; self.boss_vy = 0

                if self.boss_action == 0: 
                    self.boss_vy += GRAVITY; self.by += self.boss_vy
                    if self.by > FLOOR_Y: self.by = FLOOR_Y; self.boss_vy = 0
                    if self.bx < self.px: self.bx += 1; self.b_dir = 1
                    elif self.bx > self.px + 24: self.bx -= 1; self.b_dir = -1
                    if random.random() < 0.03 and self.by == FLOOR_Y: self.boss_vy = -12
                    self.boss_timer += 1
                    if self.boss_timer > 50:
                        self.boss_timer = 0
                        angle = math.atan2(self.py + 16 - (self.by - 20), self.px + 12 - self.bx)
                        self.boss_bullets.append([self.bx, self.by - 20, math.cos(angle)*6, math.sin(angle)*6, "web"])
                        
                elif self.boss_action == 1: # Swarm phase
                    self.swarm_timer += 1
                    if pyxel.frame_count % 2 == 0:
                        self.tiny_spiders.append([random.randint(0, 256), -10])
                        if random.random() < 0.5: self.tiny_spiders.append([random.randint(0, 256), -20])
                        
                    if self.latched_spiders > 0 and pyxel.frame_count % 30 == 0:
                        if not self.immortal: self.p_hp -= (self.latched_spiders * 2)
                        self.trigger_impact(2)
                        
                elif self.boss_action == 2: 
                    self.boss_vy += GRAVITY; self.by += self.boss_vy
                    if self.by > FLOOR_Y: 
                        if self.boss_vy > 5: self.trigger_impact(10)
                        self.by = FLOOR_Y; self.boss_vy = 0
                    
                    if self.bx < self.px: self.bx += 2; self.b_dir = 1
                    elif self.bx > self.px + 24: self.bx -= 2; self.b_dir = -1
                    
                    if abs(self.bx - (self.px + 12)) < 30 and abs((self.by - 20) - (self.py + 16)) < 30:
                        if not self.immortal and pyxel.frame_count % 15 == 0:
                            self.p_hp -= 15; self.trigger_impact(5)
                            
                    self.boss_timer += 1
                    if self.boss_timer > 30:
                        self.boss_timer = 0
                        angle = math.atan2(self.py + 16 - (self.by - 20), self.px + 12 - self.bx)
                        self.boss_bullets.append([self.bx, self.by - 20, math.cos(angle)*8, math.sin(angle)*8, "web"])

                target_x, target_y = self.px + 12, self.py + 16
                for ts in self.tiny_spiders[:]:
                    angle = math.atan2(target_y - ts[1], target_x - ts[0])
                    ts[0] += math.cos(angle) * 2.0; ts[1] += math.sin(angle) * 2.0
                    if abs(ts[0] - target_x) < 15 and abs(ts[1] - target_y) < 15:
                        self.latched_spiders += 1
                        self.spawn_particles(ts[0], ts[1], 8, 2); self.tiny_spiders.remove(ts)

                for b in self.boss_bullets[:]:
                    b[0] += b[2]; b[1] += b[3]
                    if b[4] == "web":
                        if b[1] >= FLOOR_Y:
                            self.web_traps.append([b[0], FLOOR_Y, 240]) 
                            self.play_sfx(62) 
                            if b in self.boss_bullets: self.boss_bullets.remove(b)
                            continue
                        
                        if abs(b[0] - (self.px + 12)) < 12 and abs(b[1] - (self.py + 16)) < 12:
                            if not self.immortal: self.p_hp -= 10
                            self.trigger_impact(5)
                            if b in self.boss_bullets: self.boss_bullets.remove(b)
                    elif b[0] < -50 or b[0] > SCREEN_WIDTH + 50 or b[1] < -50 or b[1] > SCREEN_HEIGHT:
                        if b in self.boss_bullets: self.boss_bullets.remove(b)

            # --- LEVEL 6: The Brawler (Wolf) ---
            elif self.level == 6:
                l6_rage = hp_pct <= 0.8
                l6_balkan = hp_pct <= 0.2

                self.boss_vy += GRAVITY
                self.by += self.boss_vy
                on_b_ground = False

                if l6_rage and not self.platforms_spawned:
                    self.platforms = [[40, 180, 40, 150, 0, 150, 150, False], [176, 180, 40, 150, 0, 150, 150, False], [108, 120, 40, 150, 0, 150, 150, False]]
                    self.platforms_spawned = True
                    self.trigger_impact(20, flash=True)
                    self.spawn_explosion(self.bx, self.by - 20, 60, 8, 2)
                    self.play_sfx(56)

                for plat in self.platforms:
                    if len(plat) > 3 and plat[3] > 0:
                        if self.boss_vy >= 0 and self.by >= plat[1] and self.by - self.boss_vy <= plat[1] + 10:
                            if self.bx + 14 > plat[0] and self.bx - 14 < plat[0] + plat[2]:
                                self.by = plat[1]; self.boss_vy = 0; on_b_ground = True

                if self.by > FLOOR_Y:
                    self.by = FLOOR_Y; self.boss_vy = 0; on_b_ground = True

                dist_x = (self.px + 12) - self.bx; dist_y = (self.py + 16) - self.by
                self.b_dir = 1 if dist_x > 0 else -1

                if l6_balkan: speed = 1.2
                elif l6_rage: speed = 3.1
                else: speed = 2.4

                if abs(dist_x) > 15: self.bx += speed * self.b_dir

                if l6_rage and dist_y < -20 and on_b_ground and random.random() < 0.1:
                    self.boss_vy = JUMP_STRENGTH * 1.2
                    self.spawn_particles(self.bx, self.by, 8, 5, gravity=True)

                if self.punch_anim > 0: self.punch_anim -= 1

                if self.punch_timer > 0:
                    self.punch_timer -= 1
                else:
                    if abs(dist_x) < 35 and abs(dist_y) < 40:
                        self.punch_anim = 10 
                        if l6_balkan:
                            self.punch_timer = random.randint(30, 60)
                            if not self.immortal: self.p_hp -= 25 
                            self.trigger_impact(15)
                            self.spawn_explosion(self.px + 12, self.py + 16, 20, 8, 2); self.play_sfx(57)
                        elif l6_rage:
                            self.combo_count += 1
                            if self.combo_count < 6:
                                self.punch_timer = 8
                                if not self.immortal: self.p_hp -= 5 
                                self.trigger_impact(3)
                            else:
                                self.punch_timer = 40; self.combo_count = 0
                                if not self.immortal: self.p_hp -= 15 
                                self.trigger_impact(12)
                                self.spawn_explosion(self.px + 12, self.py + 16, 30, 8, 10); self.play_sfx(57)
                                self.px += 40 * self.b_dir 
                        else:
                            self.punch_timer = 12
                            if not self.immortal: self.p_hp -= 8 
                            self.trigger_impact(4)

            # --- LEVEL 7: THE MALEVOLENT WITCH ---
            elif self.level == 7:
                # Phase Transition checks
                if hp_pct <= 0.1 and self.boss_action != 3:
                    self.boss_action = 3; self.boss_immune = False
                    self.spawn_explosion(self.bx, self.by - 20, 60, 8, 2) 
                    self.trigger_impact(20, flash=True); self.play_sfx(58) 
                elif hp_pct <= 0.65 and hp_pct > 0.1 and self.boss_action == 0:
                    self.boss_action = 1; self.boss_immune = True; self.witch_darkness = True
                    pyxel.stop(0); pyxel.stop(1); pyxel.stop(2) 
                    self.by = -1000  # Hide Boss
                    self.skeletons_killed = 0; self.skeletons_to_spawn = 20 
                    self.trigger_impact(30); self.play_sfx(56)
                    self.spawn_text(128, 120, "DOOM AND DESPAIR", 8)
                
                # Skeleton Horde Logic
                for sk in self.skeletons[:]:
                    sk_dist_x = (self.px + 12) - sk[0]
                    sk[3] = 1 if sk_dist_x > 0 else -1
                    sk[0] += 1.0 * sk[3]
                    if abs(sk_dist_x) < 15 and abs((self.py + 16) - sk[1]) < 20:
                        if not self.immortal and pyxel.frame_count % 30 == 0:
                            self.p_hp -= 10; self.trigger_impact(5)
                            
                # Earth Spike logic
                for sp in self.earth_spikes[:]:
                    sp[1] -= 1
                    if sp[1] <= 0 and sp[2] == 0:
                        sp[2] = 1; sp[1] = 20 
                        self.trigger_impact(8); self.play_sfx(62)
                        self.spawn_particles(sp[0], FLOOR_Y, 4, 10, gravity=True)
                    elif sp[2] == 1:
                        if abs((self.px + 12) - sp[0]) < 25 and abs((self.py + 32) - FLOOR_Y) < 30: 
                            if not self.immortal and pyxel.frame_count % 10 == 0:
                                self.p_hp -= 15; self.trigger_impact(4)
                        if sp[1] <= 0: self.earth_spikes.remove(sp)

                # Soul Head logic (Homing missiles)
                target_x, target_y = self.px + 12, self.py + 16
                for sh in self.soul_heads[:]:
                    sh[4] -= 1 
                    angle = math.atan2(target_y - sh[1], target_x - sh[0])
                    sh[2] += math.cos(angle) * 0.15; sh[3] += math.sin(angle) * 0.15
                    spd = math.hypot(sh[2], sh[3])
                    if spd > 4.5: sh[2] = (sh[2]/spd)*4.5; sh[3] = (sh[3]/spd)*4.5
                    
                    sh[0] += sh[2]; sh[1] += sh[3]
                    if abs(sh[0] - target_x) < 10 and abs(sh[1] - target_y) < 10:
                        if not self.immortal: self.p_hp -= 12
                        self.trigger_impact(6); self.spawn_explosion(sh[0], sh[1], 20, 11, 3); self.soul_heads.remove(sh)
                    elif sh[4] <= 0:
                        self.spawn_particles(sh[0], sh[1], 13, 5); self.soul_heads.remove(sh)
                        
                # Witch Projectile Logic
                for b in self.boss_bullets[:]:
                    b[0] += b[2]; b[1] += b[3]
                    if b[4] in ["witch_fireball", "witch_freeze"]:
                        if abs(b[0] - (self.px + 12)) < 12 and abs(b[1] - (self.py + 16)) < 12:
                            if not self.immortal: self.p_hp -= (20 if b[4] == "witch_fireball" else 10)
                            self.trigger_impact(8); self.spawn_explosion(b[0], b[1], 25, 8 if b[4] == "witch_fireball" else 12, 2)
                            if b in self.boss_bullets: self.boss_bullets.remove(b)
                            continue
                        if b[0] < -50 or b[0] > SCREEN_WIDTH + 50 or b[1] < -50 or b[1] > SCREEN_HEIGHT:
                            if b in self.boss_bullets: self.boss_bullets.remove(b)
                    elif b[4] == "poison":
                        if abs(b[0] - (self.px + 12)) < 15 and abs(b[1] - (self.py + 16)) < 15:
                            self.p_poisoned = True
                            self.spawn_explosion(b[0], b[1], 30, 11, 3)
                            if b in self.boss_bullets: self.boss_bullets.remove(b)
                            continue

                # Boss AI Actions based on state
                if self.boss_action == 0:
                    self.boss_timer += 1
                    self.bx += math.sin(pyxel.frame_count * 0.05) * 1.5
                    self.by = FLOOR_Y - 40 + math.sin(pyxel.frame_count * 0.1) * 5
                    
                    if self.boss_timer > 30:
                        self.boss_timer = 0
                        attack = random.randint(0, 4) 
                        if attack in [0, 4]: 
                            self.soul_heads.append([self.bx, self.by - 20, random.uniform(-2,2), -3, 150])
                            self.soul_heads.append([self.bx, self.by - 20, random.uniform(-2,2), -3, 150])
                        elif attack == 1: 
                            angle = math.atan2(self.py + 16 - (self.by - 20), self.px + 12 - self.bx)
                            self.boss_bullets.append([self.bx, self.by - 20, math.cos(angle)*7, math.sin(angle)*7, "witch_fireball"])
                        elif attack == 2: 
                            angle = math.atan2(self.py + 16 - (self.by - 20), self.px + 12 - self.bx)
                            self.boss_bullets.append([self.bx, self.by - 20, math.cos(angle)*9, math.sin(angle)*9, "witch_freeze"])
                        elif attack == 3: 
                            self.geyser_warns.append([self.px + 12, 40])
                
                elif self.boss_action == 1: # The Skeleton Phase
                    if self.skeletons_to_spawn > 0 and pyxel.frame_count % 25 == 0: 
                        sx = random.choice([-20, SCREEN_WIDTH + 20])
                        self.skeletons.append([sx, FLOOR_Y, 28, 1 if sx < 0 else -1]) 
                        self.skeletons_to_spawn -= 1
                        if random.random() < 0.3: self.play_sfx(61) 

                    if self.skeletons_to_spawn == 0 and len(self.skeletons) == 0:
                        self.boss_action = 2
                        self.bx, self.by = 128, FLOOR_Y - 40
                        self.boss_immune = False; self.witch_darkness = False
                        self.play_music() 
                        self.spawn_explosion(self.bx, self.by - 20, 50, 13, 0)
                        self.trigger_impact(15, flash=True); self.play_sfx(56)

                elif self.boss_action == 2: # Desperation phase
                    self.boss_timer += 1
                    self.bx += math.sin(pyxel.frame_count * 0.05) * 1.0
                    self.by = FLOOR_Y - 40 + math.sin(pyxel.frame_count * 0.1) * 5
                    
                    if self.boss_timer > 25: 
                        self.boss_timer = 0
                        attack = random.randint(0, 3)
                        if attack == 1 and getattr(self, 'witch_quakes', 0) >= 3:
                            attack = random.choice([0, 2, 3]) # Limit earth spikes
                            
                        if attack == 0: 
                            angle = math.atan2(self.py + 16 - (self.by - 20), self.px + 12 - self.bx)
                            self.boss_bullets.append([self.bx, self.by - 20, math.cos(angle)*5.0, math.sin(angle)*5.0, "poison"])
                        elif attack == 1: 
                            self.shake = 20; self.witch_quakes = getattr(self, 'witch_quakes', 0) + 1
                            for _ in range(5):
                                self.earth_spikes.append([random.randint(20, 236), random.randint(20, 40), 0])
                        elif attack in [2, 3]: 
                            self.soul_heads.append([self.bx, self.by - 20, random.uniform(-2,2), -3, 150])
                            
                elif self.boss_action == 3: # Enrage/Final chase
                    self.boss_vy += GRAVITY
                    self.by += self.boss_vy
                    if self.by > FLOOR_Y: self.by = FLOOR_Y; self.boss_vy = 0
                    
                    dist_x = (self.px + 12) - self.bx
                    self.b_dir = 1 if dist_x > 0 else -1
                    if abs(dist_x) > 10:
                        self.bx += 2.0 * self.b_dir
                    
                    if abs(dist_x) < 20 and abs((self.py + 16) - self.by) < 30:
                        if not self.immortal and pyxel.frame_count % 45 == 0:
                            dmg = int(self.max_hp * 0.20) 
                            self.p_hp -= dmg
                            self.trigger_impact(15); self.spawn_explosion(self.px + 12, self.py + 16, 30, 8, 2)

        # Death & Victory Checks
        if self.p_hp <= 0: self.state = 4
        if getattr(self, 'boss_hp', 1) <= 0 and self.level != 0: 
            self.state = 5; self.play_sfx(56)

    # ==========================================
    # --- RENDER/DRAW LOGIC ---
    # Pyxel calls this every frame. The order matters! (Background -> Entities -> UI)
    # ==========================================
    def draw_glow(self, x, y, radius, color_layers):
        # A helper function that draws nested circles for a glowing effect
        step = radius / len(color_layers)
        for i, col in enumerate(color_layers): pyxel.circ(x, y, radius - (i * step), col)

    def text_shadow(self, x, y, text, col):
        # Draws a black outline behind text to make it readable anywhere
        pyxel.text(x+1, y+1, text, 0); pyxel.text(x, y, text, col)
        
    def text_thick(self, x, y, text, col):
        if text is None: return
        pyxel.text(x-1, y, text, 0); pyxel.text(x+1, y, text, 0)
        pyxel.text(x, y-1, text, 0); pyxel.text(x, y+1, text, 0)
        pyxel.text(x+1, y+1, text, 0); pyxel.text(x, y, text, col)

    def draw_peaceful_background(self):
        # Level 7 Defeated: The beautiful sunset ending scene
        pyxel.cls(9) 
        
        # Sunset gradient
        for y in range(160):
            if y < 60: col = 14 
            elif y < 110: col = 8 
            else: col = 9 
            pyxel.line(0, y, 256, y, col)
            
        # The Sun
        pyxel.circ(128, 120, 25, 10); pyxel.circ(128, 120, 20, 9); pyxel.circ(128, 120, 15, 7)
        
        # Mountains
        pyxel.tri(-30, 180, 60, 60, 140, 180, 5) 
        pyxel.tri(90, 180, 180, 40, 270, 180, 1) 
        pyxel.tri(200, 180, 240, 90, 290, 180, 5) 
        pyxel.pset(60, 61, 7); pyxel.pset(180, 41, 7)
        
        # Grass ground
        pyxel.rect(0, 170, 256, 86, 14) 
        for y in range(174, SCREEN_HEIGHT, 4):
            if y % 8 == 0: pyxel.rect(0, y, 256, 4, 3) 
            else: pyxel.rect(0, y, 256, 4, 14)
            
        for f in getattr(self, 'flowers', []):
            sway = math.sin(pyxel.frame_count * 0.04 + f["y"] * 0.1 + f["sway_offset"]) * 1.5
            fx = f["x"] + sway; fy = f["y"]; col = f["color"]
            
            pyxel.line(fx, fy, fx - sway*0.5, fy+8, 3) 
            if f["size"] == 1:
                pyxel.pset(fx, fy, col); pyxel.pset(fx, fy-1, 7) 
            else:
                pyxel.circ(fx, fy, 1, col); pyxel.pset(fx, fy, 7) 
        
        for b in getattr(self, 'butterflies', []):
            bx, by = int(b["x"]), int(b["y"]); col = b["color"]
            flapping = b["flap_timer"] % 20 < 10
            
            if flapping:
                pyxel.line(bx, by-3, bx, by+3, 0); pyxel.rect(bx-1, by-2, 3, 5, col)
            else:
                pyxel.tri(bx-4, by-2, bx, by, bx-2, by+3, col); pyxel.tri(bx+4, by-2, bx, by, bx+2, by+3, col)
        
        for bird in getattr(self, 'bird_flock', []):
            bx, by_final = int(bird["x"]), int(bird["y"])
            wing_sway = math.sin(pyxel.frame_count * 0.2 + bird["w_offset"])
            
            pyxel.rect(bx, by_final, 4, 2, 0) 
            if wing_sway > 0:
                pyxel.tri(bx+1, by_final, bx-3, by_final-4, bx+3, by_final-4, 0)
            else:
                pyxel.tri(bx+1, by_final+2, bx-3, by_final+6, bx+3, by_final+6, 0)
            
        # Draw Autumn Leaves (Same shapes as normal, but Autumn colors)
        for leaf in getattr(self, 'autumn_leaves', []):
            lx, ly = int(leaf[0]), int(leaf[1])
            l_type = leaf[3] # 0 = slender, 1 = round, 2 = standard
            c1, c2 = leaf[4], leaf[5]
            
            if l_type == 0:
                pyxel.line(lx, ly, lx+3, ly+1, c1); pyxel.pset(lx+1, ly+1, c2)
            elif l_type == 1:
                pyxel.rect(lx, ly, 2, 2, c2); pyxel.pset(lx+2, ly, c1); pyxel.pset(lx-1, ly+1, c1)
            else:
                pyxel.rect(lx, ly, 3, 2, c1); pyxel.pset(lx-1, ly, c2); pyxel.pset(lx+3, ly+1, c2)
        
        for ff in getattr(self, 'fireflies', []):
            blink = ff[2] % 60
            if blink < 30: intensity = blink / 30.0 
            else: intensity = (60 - blink) / 30.0 
                
            if intensity > 0.2:
                self.draw_glow(int(ff[0]), int(ff[1]), 2, [0, 5, 11]); pyxel.pset(int(ff[0]), int(ff[1]), 7) 
        
        # Giant background tree silhouette
        tx, ty = 30, 100
        pyxel.rect(tx, ty+5, 15, 70, 0); pyxel.rect(tx+3, ty+8, 9, 64, 4) 
        pyxel.circ(tx+7, ty, 35, 0); pyxel.circ(tx+7, ty, 32, 3) 
        pyxel.circ(tx-10, ty+20, 25, 0); pyxel.circ(tx-10, ty+20, 22, 3) 
        pyxel.circ(tx+25, ty+15, 20, 0); pyxel.circ(tx+25, ty+15, 17, 3) 
        for _ in range(50):
            pyxel.pset(tx + random.randint(-30, 40), ty + random.randint(-30, 40), random.choice([3, 11]))
        
        # Draw the blood on the ground where the boss was defeated
        for drip in getattr(self, 'mage_blood_drips', []):
            if len(drip) >= 2: pyxel.pset(int(drip[0]), int(drip[1]), 8)
            
        # Draw The Victorious Warlock
        mx, my = 128 - 12, 170 - 32
        robe_core = 12; robe_trim = 4; battle_blood = 8
        
        pyxel.rect(mx + 2, my + 30, 20, 4, 0) 
        pyxel.rect(mx+4, my+12, 16, 18, battle_blood) 
        pyxel.rect(mx+8, my+12, 8, 18, robe_core) 
        pyxel.pset(mx+5, my+15, battle_blood); pyxel.pset(mx+19, my+20, battle_blood); pyxel.pset(mx+7, my+25, battle_blood)
        pyxel.rect(mx-2, my+18, 6, 8, robe_trim) 
        pyxel.rect(mx-1, my+19, 4, 6, 7) 

        pyxel.line(mx+6, my+14, mx+6, my+28, 13) 
        pyxel.line(mx+18, my+14, mx+18, my+28, 13) 
        pyxel.rect(mx+4, my+22, 16, 3, robe_trim)
        pyxel.rect(mx+10, my+21, 6, 5, 10) 
        pyxel.rect(mx+11, my+22, 4, 3, 0) 
        pyxel.tri(mx+4, my+12, mx+20, my+12, mx+12, my+26, 7)
        pyxel.line(mx+8, my+12, mx+12, my+24, 6) 
        pyxel.line(mx+16, my+12, mx+12, my+24, 6)
        pyxel.rect(mx+8, my+6, 8, 6, 15)
        pyxel.pset(mx+10, my+8, 0); pyxel.pset(mx+14, my+8, 0) 
        pyxel.line(mx+9, my+6, mx+15, my+6, robe_trim) 
        pyxel.tri(mx+2, my+6, mx+22, my+6, mx+12, my-12, robe_core)
        pyxel.tri(mx+6, my+4, mx+18, my+4, mx+12, my-8, 1) 
        pyxel.rect(mx, my+6, 24, 3, robe_core) 
        pyxel.line(mx, my+8, mx+24, my+8, 1) 

        staff_x = mx + 24
        pyxel.line(staff_x, my + 8, staff_x, my + 30, 4)
        pyxel.circ(staff_x, my + 6, 4, 8)
        self.draw_glow(staff_x, my + 6, 10, [1, 12, 11, 7])
        
        # The landing dove animation
        if getattr(self, 'dove', None):
            dx, dy = int(self.dove["x"]), int(self.dove["y"])
            if self.dove["state"] == "flying":
                flap = math.sin(pyxel.frame_count * 0.5)
                pyxel.rect(dx, dy, 6, 3, 7) 
                pyxel.pset(dx+5, dy-1, 7); pyxel.pset(dx+6, dy-1, 8) 
                if flap > 0:
                    pyxel.tri(dx+2, dy, dx-2, dy-5, dx+4, dy-6, 7)
                else:
                    pyxel.tri(dx+2, dy+2, dx-2, dy+7, dx+4, dy+6, 7) 
            else:
                pyxel.rect(dx-2, dy-2, 6, 4, 7) 
                pyxel.rect(dx+2, dy-4, 3, 3, 7) 
                pyxel.pset(dx+5, dy-3, 8) 
                pyxel.line(dx, dy, dx-4, dy+3, 7) 
        
        for p in self.particles: pyxel.pset(p[0], p[1], p[5])

    def draw_epic_background(self):
        # The main dark, moody background for all fighting phases
        t = pyxel.frame_count
        pyxel.cls(0)
        
        # Background coloring shifts based on level/boss HP
        if getattr(self, 'level', 1) == 6:
            bg_col = 2 if (self.boss_hp / self.boss_max_hp) <= 0.8 else 1
            for y in range(80):
                if y % 4 == 0: pyxel.line(0, y, 256, y, bg_col)
            pyxel.rect(0, 80, 256, 80, bg_col)
        elif getattr(self, 'level', 1) == 7:
            for y in range(80):
                if y % 5 == 0: pyxel.line(0, y, 256, y, 13)
            pyxel.rect(0, 80, 256, 80, 0)
        else:
            for y in range(80):
                if y > 60: pyxel.line(0, y, 256, y, 1)
                elif y > 40 and y % 2 == 0: pyxel.line(0, y, 256, y, 1)
                elif y > 20 and y % 3 == 0: pyxel.line(0, y, 256, y, 1)
                elif y % 4 == 0: pyxel.line(0, y, 256, y, 1)
            pyxel.rect(0, 80, 256, 80, 1)

        if getattr(self, 'level', 1) == 5 and getattr(self, 'boss_action', 0) == 1:
            cx, cy = 128, 120
            pyxel.circ(cx, cy, 70, 2) 
            pyxel.circ(cx, cy-50, 40, 0) 
            leg_t = t * 0.02
            for i in range(8):
                ang = (i/8.0) * math.pi * 2 + leg_t
                dist = 160 
                ex = cx + math.cos(ang) * dist; ey = cy + math.sin(ang) * dist
                mid_x = cx + math.cos(ang) * (dist*0.6); mid_y = cy + math.sin(ang) * (dist*0.3)
                pyxel.line(cx, cy, mid_x, mid_y, 0); pyxel.line(mid_x, mid_y, ex, ey, 2) 

        mx, my = 40, 40
        if getattr(self, 'level', 1) == 6:
            pyxel.circ(mx, my, 16, 8); pyxel.circ(mx - 4, my - 4, 14, 2); pyxel.circb(mx, my, 18, 2)
        elif getattr(self, 'level', 1) == 7:
            pyxel.circ(mx, my, 16, 13); pyxel.circ(mx - 2, my - 2, 14, 0); pyxel.circb(mx, my, 18, 5) 
        else:
            pyxel.circ(mx, my, 16, 10); pyxel.circ(mx - 4, my - 4, 14, 0); pyxel.circb(mx, my, 18, 9) 

        for i in range(4):
            cx = ((t * (0.2 + i*0.1)) % 350) - 50; cy = 15 + i * 20
            pyxel.rect(cx, cy, 50 + i*15, 3 + i%2, 5); pyxel.rect(cx + 10, cy - 2, 30 + i*10, 2, 1)

        bx, by = int(self.bird["x"]), int(self.bird["y"])
        pyxel.line(bx, by, bx-4, by-3, 0); pyxel.line(bx, by, bx+4, by-3, 0)
        if t % 10 < 5: pyxel.line(bx, by, bx-4, by+2, 0); pyxel.line(bx, by, bx+4, by+2, 0)

        for i in range(5):
            px = (i * 65 - (t * 0.05) % 65) - 40
            pyxel.tri(px, 160, px+40, 70+i*10, px+80, 160, 2 if self.level != 7 else 1) 
            pyxel.tri(px+40, 70+i*10, px+55, 100+i*10, px+80, 160, 3 if self.level != 7 else 5) 

        cast_x, cast_y = 150, 130
        if getattr(self, 'level', 1) != 7:
            pyxel.rect(cast_x, cast_y-50, 40, 80, 0); pyxel.tri(cast_x-10, cast_y+30, cast_x+20, cast_y, cast_x+50, cast_y+30, 0)
            pyxel.rect(cast_x-5, cast_y-70, 12, 50, 0); pyxel.tri(cast_x-7, cast_y-70, cast_x+1, cast_y-95, cast_x+9, cast_y-70, 0)
            pyxel.rect(cast_x+33, cast_y-60, 12, 40, 0); pyxel.tri(cast_x+31, cast_y-60, cast_x+39, cast_y-85, cast_x+47, cast_y-60, 0)
            pyxel.rect(cast_x+2, cast_y-40, 6, 60, 5); pyxel.rect(cast_x+35, cast_y-30, 6, 50, 5)
            glow = 8 if getattr(self, 'level', 1) == 6 else (9 if (t % 30 < 15) else 10)
            pyxel.rect(cast_x+12, cast_y-35, 16, 20, glow); pyxel.circ(cast_x+20, cast_y-35, 8, glow)
            pyxel.line(cast_x+20, cast_y-43, cast_x+20, cast_y-15, 0); pyxel.line(cast_x+12, cast_y-25, cast_x+28, cast_y-25, 0)

        if self.whale["active"]:
            wx, wy = int(self.whale["x"]), int(self.whale["y"])
            pyxel.circ(wx, wy, 8, 0); pyxel.circ(wx - 4, wy + 2, 6, 1) 
            tail_angle = math.atan2(self.whale["vy"], self.whale["vx"]) + 3.14
            tx = wx + math.cos(tail_angle) * 10; ty = wy + math.sin(tail_angle) * 10
            pyxel.line(wx, wy, tx, ty, 0); pyxel.tri(tx, ty, tx-5, ty-5, tx-2, ty+6, 0)

        for y in range(160, 200):
            wave_offset = math.sin(t * 0.05 + y * 0.1) * (y - 150) * 0.05 
            pyxel.line(0, y, 256, y, 1) 
            reflect_width = (y - 150) * 0.3
            if (y + t//4) % 3 != 0: 
                pyxel.line(40 - reflect_width + wave_offset, y, 40 + reflect_width + wave_offset, y, 5 if y%3==0 else 1)

        for s in self.water_splashes: pyxel.circb(s[0], s[1], s[2], 7 if s[2] < 2 else 5)
        for m in self.mist: pyxel.line(m[0], m[1], m[0] + 15, m[1], 5 if m[1]%2==0 else 1)
        pyxel.rect(0, 200, 256, 56, 4) 
        for dy in range(200, 256):
            if dy % 2 == 0: pyxel.line(0, dy, 256, dy, 0) 
        for i in range(16):
            px = (i * 18 - (t * 0.5) % 18) - 10
            tree_height = 180 + (i % 3) * 15
            pyxel.line(px, 256, px, tree_height, 0) 
            for ty in range(tree_height, 256, 3):
                w = (ty - tree_height) * 0.4
                pyxel.line(px - w, ty, px + w, ty, 3); pyxel.line(px - w + 1, ty + 1, px + w - 1, ty + 1, 0) 
                
        # Draw Normal Gameplay Leaves (Always Green variants, 3 shapes)
        for l in getattr(self, 'leaves', []):
            lx, ly = int(l[0]), int(l[1])
            l_type = l[2] if len(l) > 2 else int(lx) % 3
            c1, c2 = 11, 3 # Light green and Dark green
            if l_type == 0:
                pyxel.line(lx, ly, lx+3, ly+1, c1); pyxel.pset(lx+1, ly+1, c2)
            elif l_type == 1:
                pyxel.rect(lx, ly, 2, 2, c2); pyxel.pset(lx+2, ly, c1); pyxel.pset(lx-1, ly+1, c1)
            else:
                pyxel.rect(lx, ly, 3, 2, c1); pyxel.pset(lx-1, ly, c2); pyxel.pset(lx+3, ly+1, c2)
            
        for r in self.rain: 
            rain_col = 5 if r[1] % 2 == 0 else 1
            if getattr(self, 'level', 1) == 6 and r[1] % 2 == 0: rain_col = 8
            if getattr(self, 'level', 1) == 7: rain_col = 13
            pyxel.line(r[0], r[1], r[0], r[1]+2, rain_col)

    def draw_detailed_mage(self):
        # Draws our Player Character (The Warlock)
        robe_core = 12 if not self.p_poisoned else 11
        robe_trim = 4 if not self.p_poisoned else 3
        
        pyxel.rect(self.px + 2, self.py + 30, 20, 3, 0) 
        pyxel.rect(self.px-2, self.py+18, 6, 8, robe_trim)
        pyxel.rect(self.px-1, self.py+19, 4, 6, 7) 
        pyxel.line(self.px-1, self.py+22, self.px+2, self.py+22, 8) 
        pyxel.rect(self.px+4, self.py+12, 16, 18, robe_core)
        pyxel.rect(self.px+8, self.py+12, 8, 18, 1) 
        pyxel.line(self.px+6, self.py+14, self.px+6, self.py+28, 13) 
        pyxel.line(self.px+18, self.py+14, self.px+18, self.py+28, 13) 
        pyxel.rect(self.px+4, self.py+22, 16, 3, robe_trim)
        pyxel.rect(self.px+10, self.py+21, 6, 5, 10) 
        pyxel.rect(self.px+11, self.py+22, 4, 3, 0) 
        pyxel.tri(self.px+4, self.py+12, self.px+20, self.py+12, self.px+12, self.py+26, 7)
        pyxel.line(self.px+8, self.py+12, self.px+12, self.py+24, 6) 
        pyxel.line(self.px+16, self.py+12, self.px+12, self.py+24, 6)
        pyxel.rect(self.px+8, self.py+6, 8, 6, 15)
        pyxel.pset(self.px+10, self.py+8, 0); pyxel.pset(self.px+14, self.py+8, 0) 
        pyxel.line(self.px+9, self.py+6, self.px+15, self.py+6, robe_trim) 
        pyxel.tri(self.px+2, self.py+6, self.px+22, self.py+6, self.px+12, self.py-12, robe_core)
        pyxel.tri(self.px+6, self.py+4, self.px+18, self.py+4, self.px+12, self.py-8, 1) 
        pyxel.rect(self.px, self.py+6, 24, 3, robe_core) 
        pyxel.line(self.px, self.py+8, self.px+24, self.py+8, 1) 
        
        # Draw dynamic aiming line for magic
        dx = pyxel.mouse_x - (self.px + 12); dy = pyxel.mouse_y - (self.py + 16)
        dist = math.hypot(dx, dy)
        if dist != 0:
            sx = self.px + 12 + (dx / dist) * 16; sy = self.py + 16 + (dy / dist) * 16
            pyxel.line(self.px + 11, self.py + 16, sx-1, sy, 4)
            pyxel.line(self.px + 12, self.py + 16, sx, sy, 9) 
            pyxel.line(self.px + 13, self.py + 16, sx+1, sy, 4)
            self.draw_glow(sx, sy, 8, [1, 12, 11, 7]) 
            pyxel.pset(sx, sy, 7) 
            
        for i in range(getattr(self, 'latched_spiders', 0)):
            ox = self.px + random.randint(0, 20); oy = self.py + random.randint(0, 30)
            pyxel.circ(ox, oy, 1, 0); pyxel.pset(ox, oy, 8)

    def draw_detailed_boss(self):
        # Master function to draw whichever boss is currently active
        if getattr(self, 'boss_action', 0) == 1 and getattr(self, 'level', 1) == 7: return 
            
        hp_pct = self.boss_hp / self.boss_max_hp
        rage = hp_pct < 0.5 if self.level != 4 else hp_pct < 0.4
        t = pyxel.frame_count
        dist_x = (self.px + 12) - self.bx 
        is_frozen = getattr(self, 'boss_frozen_timer', 0) > 0
        
        # Set dynamic boss size (for drawing bounds)
        bw, bh = 40, 50
        if self.level == 1: bw, bh = 30, 30
        elif self.level in [2, 3]: bw, bh = 40, 60
        elif self.level == 4: bw, bh = 40, 40
        elif self.level == 5: bw, bh = 40, 30
        elif self.level == 6: bw, bh = 30, 45
        elif self.level == 7: bw, bh = 25, 55
        elif self.level == 0: bw, bh = 20, 40

        if is_frozen: self.draw_glow(self.bx, self.by - bh//2, max(bw, bh), [1, 12, 7])
        elif getattr(self, 'boss_on_fire', 0) > 0: self.text_shadow(self.bx-10, self.by-bh-20, "ABLAZE", 8)
        
        if getattr(self, 'boss_freeze_stacks', 0) > 0 and not is_frozen:
            self.text_shadow(self.bx-10, self.by-bh-10, f"FROST {self.boss_freeze_stacks}/3", 12)
        
        # --- Boss Model Drawings ---
        if self.level == 0:
            pyxel.rect(self.bx - 8, self.by - 30, 16, 30, 4) 
            pyxel.circ(self.bx, self.by - 35, 10, 15) 
            pyxel.circ(self.bx - 4, self.by - 35, 2, 0); pyxel.circ(self.bx + 4, self.by - 35, 2, 0)
        elif self.level == 1:
            bob = math.sin(pyxel.frame_count * 0.1) * 4
            by_off = self.by + bob
            pyxel.rect(self.bx - 6, by_off + 15, 12, 4, 0); pyxel.rect(self.bx - 12, by_off - 12, 24, 24, 0) 
            pyxel.rect(self.bx - 8, by_off - 8, 16, 16, 1)
            for i in range(4): pyxel.line(self.bx - 12 + i*6, by_off + 12, self.bx - 10 + i*6, by_off + 20, 0) 
            pyxel.rect(self.bx - 16, by_off - 8, 4, 8, 13); pyxel.rect(self.bx + 12, by_off - 8, 4, 8, 13)
            pyxel.rect(self.bx - 6, by_off - 6, 12, 8, 0) 
            self.draw_glow(self.bx - 3, by_off - 4, 4, [0, 2, 8]); self.draw_glow(self.bx + 3, by_off - 4, 4, [0, 2, 8]) 
            
        elif self.level in [2, 3]:
            arm_col = 5 if not rage else 1 
            vis_col = 8 
            by_off = self.by - 10
            walk_cycle = math.sin(pyxel.frame_count * 0.2) * 4 if getattr(self, 'boss_action', 0) in [0, 1] else 0
            
            pyxel.rect(self.bx - 10, by_off + 10, 20, 4, 0) 
            pyxel.rect(self.bx - 14, by_off - 44, 28, 40, 8); pyxel.rect(self.bx - 12, by_off - 44, 24, 40, 2) 
            pyxel.line(self.bx - 14, by_off - 4, self.bx - 10, by_off + 8, 8); pyxel.line(self.bx + 14, by_off - 4, self.bx + 10, by_off + 8, 8)
            pyxel.rect(self.bx - 10, by_off - 46, 20, 36, arm_col); pyxel.rect(self.bx - 6, by_off - 46, 12, 36, 1) 
            pyxel.line(self.bx - 10, by_off - 28, self.bx + 10, by_off - 28, 0); pyxel.line(self.bx - 10, by_off - 16, self.bx + 10, by_off - 16, 0)
            self.draw_glow(self.bx, by_off - 36, 6, [0, 2, 8, 9] if rage else [0, 1, 12, 11])
            pyxel.rect(self.bx - 8 - walk_cycle, by_off - 10, 6, 20, arm_col); pyxel.rect(self.bx + 2 + walk_cycle, by_off - 10, 6, 20, arm_col) 
            pyxel.rect(self.bx - 8, by_off - 56, 16, 14, arm_col) 
            pyxel.rect(self.bx - 12, by_off - 60, 4, 10, arm_col); pyxel.pset(self.bx - 12, by_off - 62, 6) 
            pyxel.rect(self.bx + 8, by_off - 60, 4, 10, arm_col); pyxel.pset(self.bx + 11, by_off - 62, 6) 
            pyxel.rect(self.bx - 4, by_off - 50, 8, 3, vis_col) 
            if rage: self.draw_glow(self.bx, by_off - 49, 8, [0, 2, 8])
            pyxel.rect(self.bx - 16, by_off - 46, 6, 12, arm_col); pyxel.rect(self.bx + 10, by_off - 46, 6, 12, arm_col) 

            if self.level == 2:
                sx, sy = self.bx + (12 * self.b_dir), by_off - 20
                ex = sx + math.cos(self.swing_angle) * 50; ey = sy + math.sin(self.swing_angle) * 50
                gx1 = sx + math.cos(self.swing_angle + 1.57) * 10; gy1 = sy + math.sin(self.swing_angle + 1.57) * 10
                gx2 = sx - math.cos(self.swing_angle + 1.57) * 10; gy2 = sy - math.sin(self.swing_angle + 1.57) * 10
                pyxel.line(gx1, gy1, gx2, gy2, 4); pyxel.pset(gx1, gy1, 8); pyxel.pset(gx2, gy2, 8) 
                pyxel.line(sx, sy, ex, ey, 7); pyxel.line(sx+1, sy+1, ex+1, ey+1, 10); pyxel.line(sx-1, sy-1, ex-1, ey-1, 8)
                pyxel.line(sx, sy, sx + math.cos(self.swing_angle)*30, sy + math.sin(self.swing_angle)*30, 0) 
                if rage: self.draw_glow(ex, ey, 12, [0, 8, 9, 10]) 
                
            elif self.level == 3:
                gx, gy = self.bx, by_off - 26
                gun_len = 24 if not rage else 30
                ex = gx + math.cos(self.boss_aim_angle) * gun_len; ey = gy + math.sin(self.boss_aim_angle) * gun_len
                pyxel.line(gx, gy, ex, ey, 0 if not rage else 2); pyxel.line(gx, gy+1, ex, ey+1, 5) 
                offset = pyxel.frame_count % 3
                pyxel.line(gx + math.cos(self.boss_aim_angle)*offset, gy + math.sin(self.boss_aim_angle)*offset, ex, ey, 6)
                pyxel.circ(ex, ey, 3 if not rage else 5, 13 if not rage else 7); pyxel.circ(ex, ey, 1, 0) 
                pyxel.line(gx, gy, self.bx, by_off - 10, 4)
                for i in range(0, 10, 3): pyxel.pset(gx + (self.bx-gx)*(i/10.0), gy + ((by_off-10)-gy)*(i/10.0), 10)

        elif self.level == 4:
            by_off = self.by - 20
            fat_col = 8 if not (rage and self.lava_timer >= 330) else 2 
            pyxel.rect(self.bx - 20, by_off - 20, 40, 40, 0); pyxel.rect(self.bx - 18, by_off - 18, 36, 36, fat_col) 
            pyxel.rect(self.bx - 10, by_off - 10, 20, 20, 1 if fat_col == 8 else 8); pyxel.rect(self.bx - 12, by_off - 12, 24, 4, 0)
            if self.boss_action == 0: 
                t = pyxel.frame_count * 0.5 * self.b_dir
                for i in range(4):
                    sx = self.bx + math.cos(t + i*1.57) * 22; sy = by_off + math.sin(t + i*1.57) * 22
                    pyxel.circ(sx, sy, 3, 13)
            else: 
                pyxel.rect(self.bx - 26, by_off - 10, 8, 20, fat_col); pyxel.rect(self.bx + 18, by_off - 10, 8, 20, fat_col)
                
        elif self.level == 5:
            if getattr(self, 'boss_action', 0) != 1: 
                by_off = self.by - 15
                t = pyxel.frame_count * 0.3
                for i in range(4):
                    lx = self.bx + math.cos(t + i) * 20; ly = by_off + math.sin(t + i) * 10
                    pyxel.line(self.bx, by_off, lx, ly, 2); pyxel.line(lx, ly, lx + (5 * self.b_dir), ly + 15, 0)
                    rx = self.bx - math.cos(t + i) * 20; ry = by_off + math.sin(t + i) * 10
                    pyxel.line(self.bx, by_off, rx, ry, 2); pyxel.line(rx, ry, rx - (5 * self.b_dir), ry + 15, 0)
                pyxel.circ(self.bx, by_off-5, 12, 0); pyxel.circ(self.bx, by_off-5, 10, 2)
                pyxel.circ(self.bx + (8*self.b_dir), by_off-2, 6, 0); pyxel.pset(self.bx + (10*self.b_dir), by_off-4, 8)
                pyxel.pset(self.bx + (12*self.b_dir), by_off-2, 8); pyxel.pset(self.bx + (10*self.b_dir), by_off, 8)

        elif self.level == 6:
            l6_rage = hp_pct <= 0.8
            l6_balkan = hp_pct <= 0.2
            by_off = self.by - 10
            
            walk_cycle = math.sin(pyxel.frame_count * 0.4) * 4 if abs(dist_x) > 15 else 0
            if l6_balkan: self.draw_glow(self.bx, by_off - 20, 30, [0, 1, 5, 12])

            pyxel.rect(self.bx - 12, by_off + 10, 24, 4, 0) 
            arm_col = 1; armor_col = 5 if not l6_rage else 1; glove_col = 5 if not l6_rage else 8
            pyxel.rect(self.bx - 8 - walk_cycle, by_off - 10, 6, 20, armor_col); pyxel.rect(self.bx + 2 + walk_cycle, by_off - 10, 6, 20, armor_col) 
            pyxel.rect(self.bx - 14, by_off - 35, 28, 25, 0); pyxel.rect(self.bx - 12, by_off - 34, 24, 15, armor_col) 
            pyxel.rect(self.bx - 9, by_off - 19, 18, 10, armor_col) 
            
            if l6_rage: 
                pyxel.pset(self.bx - 6, by_off - 25, 8); pyxel.pset(self.bx + 8, by_off - 28, 8)
                pyxel.pset(self.bx - 2, by_off - 20, 8); pyxel.pset(self.bx + 5, by_off - 15, 8)

            pyxel.rect(self.bx - 7, by_off - 46, 14, 12, 0); pyxel.rect(self.bx - 6, by_off - 45, 12, 10, armor_col)
            eye_col = 8 if l6_rage else 13
            b_dir = 1 if dist_x > 0 else -1
            pyxel.rect(self.bx - 4 + (2*b_dir), by_off - 42, 3, 2, eye_col); pyxel.rect(self.bx + 2 + (2*b_dir), by_off - 42, 3, 2, eye_col)

            punch_ext = 20 if getattr(self, 'punch_anim', 0) > 0 else 0
            back_x = self.bx - (10 * b_dir)
            pyxel.rect(back_x - 3, by_off - 32, 6, 15, arm_col); pyxel.rect(back_x - 5, by_off - 22, 10, 10, glove_col) 
            pyxel.rect(back_x - 4, by_off - 21, 8, 8, 2 if not l6_rage else 8)

            front_x = self.bx + (10 * b_dir)
            if punch_ext > 0:
                if b_dir == 1: pyxel.rect(front_x, by_off - 28, punch_ext, 6, arm_col)
                else: pyxel.rect(front_x - punch_ext, by_off - 28, punch_ext, 6, arm_col)
            glove_x = front_x + (punch_ext * b_dir)
            pyxel.rect(glove_x - 6, by_off - 32, 12, 12, glove_col); pyxel.rect(glove_x - 5, by_off - 31, 10, 10, 2 if not l6_rage else 8)

        elif self.level == 7:
            by_off = self.by
            phase_4 = getattr(self, 'boss_action', 0) == 3
            float_y = by_off if phase_4 else by_off + math.sin(t * 0.1) * 3
            
            pyxel.rect(self.bx - 4, float_y - 45, 8, 40, 0); pyxel.rect(self.bx - 3, float_y - 44, 6, 38, 1)
            
            b_dir = 1 if dist_x > 0 else -1
            head_x = self.bx + (3 * b_dir if phase_4 else 0)
            head_y = float_y - 55 if phase_4 else float_y - 50
            pyxel.circ(head_x, head_y, 6, 0); pyxel.circ(head_x, head_y, 5, 13)
            pyxel.pset(head_x - 2 + (b_dir), head_y - 1, 8); pyxel.pset(head_x + 2 + (b_dir), head_y - 1, 8) 
            pyxel.line(head_x - 4, head_y - 5, head_x - 8, head_y + 5, 0); pyxel.line(head_x + 4, head_y - 5, head_x + 8, head_y + 5, 0)

            if phase_4:
                pyxel.line(self.bx, float_y - 30, self.bx + (15 * b_dir), float_y - 10, 13)
                pyxel.circ(self.bx + (15 * b_dir), float_y - 10, 2, 8) 
                pyxel.line(self.bx, float_y - 10, self.bx - (10 * b_dir), float_y, 13)
            else:
                pyxel.line(self.bx - 4, float_y - 35, self.bx - 12, float_y - 25, 13); pyxel.line(self.bx - 12, float_y - 25, self.bx - 10, float_y - 10, 13)
                pyxel.line(self.bx + 4, float_y - 35, self.bx + 15, float_y - 25, 13); pyxel.line(self.bx + 15, float_y - 25, self.bx + 12, float_y - 15, 13)
                staff_x = self.bx + 12
                pyxel.line(staff_x, float_y + 10, staff_x, float_y - 45, 4)
                pyxel.circ(staff_x, float_y - 47, 4, 7); pyxel.pset(staff_x - 1, float_y - 48, 0); pyxel.pset(staff_x + 1, float_y - 48, 0)
                self.draw_glow(staff_x, float_y - 47, 8, [0, 13, 5])

        if is_frozen:
            ix = self.bx - bw//2; iy = self.by - bh
            pyxel.rectb(ix, iy, bw, bh, 7); pyxel.rectb(ix-1, iy-1, bw+2, bh+2, 12); pyxel.rectb(ix+1, iy+1, bw-2, bh-2, 12)
            pyxel.line(ix, iy, ix+bw, iy+bh, 12); pyxel.line(ix+bw, iy, ix, iy+bh, 12)
            self.text_thick(self.bx-15, iy-10, "FROZEN", 12)

    # --- MASTER DRAW CALL ---
    def draw(self):
        # 1. Error Handling Check
        if self.state == 7 and not getattr(self, 'credits_list', None):
            pyxel.cls(0)
            pyxel.text(10, 10, "DATA RELOAD ERROR", 8); pyxel.text(10, 20, "PLEASE RESTART THE LEVEL", 7)
            return

        # 2. Camera FX
        if self.shake > 0: pyxel.camera(random.randint(-self.shake, self.shake)//2, random.randint(-self.shake, self.shake)//2)
        else: pyxel.camera(0, 0)
        if self.flash > 0: pyxel.cls(7); return 
        
        # 3. Draw Environments (Background layer)
        if self.state == 7: self.draw_peaceful_background()
        else: self.draw_epic_background()
        
        t = pyxel.frame_count
        
        # 4. Render UI Overlays based on state
        if self.state == 0:
            self.text_shadow(60, 80, "THE MALEVOLENT WITCH", t % 16)
            self.text_shadow(80, 110, "A) DEPLOY WARLOCK", 7); self.text_shadow(80, 125, "B) UPGRADES", 7)
            self.text_shadow(80, 140, "C) DEV PANEL", 7); self.text_shadow(80, 155, "D) SETTINGS", 10)
            self.text_shadow(80, 170, "E) TUTORIAL", 11)
            
        elif self.state == 7:
            current_credits = self.credits_list if self.credits_list else []
            for i in range(len(current_credits)):
                line = current_credits[i]
                if line is None: continue 
                x = SCREEN_WIDTH//2 - (len(line) * 2); y = int(self.credits_y + i * 40)
                
                if -10 < y < SCREEN_HEIGHT:
                    line_color = 7 
                    if "Tony" in line and "Tony" != line:
                        parts = line.split("Tony")
                        pyxel.text(x, y, parts[0], 5); pyxel.text(x + len(parts[0])*4, y, "Tony", 10) 
                        continue
                    elif "Jashan" in line and "Jashan" != line:
                        parts = line.split("Jashan")
                        pyxel.text(x, y, parts[0], 13); pyxel.text(x + len(parts[0])*4, y, "Jashan", 11) 
                        continue
                    elif "COMMANDER" in line or "PROFILE" in line or "LOG" in line: line_color = 13 
                    elif "THE MALEVOLENT WITCH" == line: line_color = 8 
                    self.text_thick(x, y, line, line_color)
            
        elif self.state == 6:
            self.text_shadow(20, 20, "--- AUDIO SETTINGS ---", 11)
            self.text_shadow(20, 50, "MUSIC VOLUME", 7); self.text_shadow(20, 65, f"< {self.music_vol} / 7 >", 10)
            self.text_shadow(80, 65, "[UP / DOWN]", 5); self.text_shadow(20, 95, "SFX VOLUME", 7)
            self.text_shadow(20, 110, f"< {self.sfx_vol} / 7 >", 10); self.text_shadow(80, 110, "[LEFT / RIGHT]", 5)
            self.text_shadow(20, 220, "BACKSPACE TO EXIT", 6)
            
        elif self.state == 1:
            self.text_shadow(20, 20, f"COINS: {self.coins}", 10); self.text_shadow(20, 40, "-- CORE STATS --", 6)
            self.text_shadow(20, 55, f"1) DMG LVL {self.damage_lv} ({self.damage_lv*50}c)", 7)
            self.text_shadow(20, 70, f"2) SPD LVL {self.proj_speed_lv} ({self.proj_speed_lv*30}c)", 7)
            self.text_shadow(20, 85, f"3) MAX HP LVL {self.hp_lv} ({self.hp_lv*40}c)", 7)
            self.text_shadow(20, 110, f"-- SPELLS (ACTIVE: {self.active_spell or 'NONE'}) --", 6)
            
            def spell_txt(num, name, cost, y):
                state = "[ACTIVE]" if self.active_spell == name else ("[OWNED]" if name in self.unlocked_spells else f"({cost}c)")
                col = 10 if self.active_spell == name else (7 if name in self.unlocked_spells else 5)
                self.text_shadow(20, y, f"{num}) {name} {state}", col)
                
            spell_txt(4, "Teleport", 100, 125); spell_txt(5, "Fireball", 150, 140)
            spell_txt(6, "Freeze", 150, 155); spell_txt(7, "Heal", 200, 170)
            self.text_shadow(20, 220, "BACKSPACE TO EXIT", 6)
            
        elif self.state == 2:
            self.text_shadow(20, 20, "DEV UNLOCKED (GOD MODE)", 11); self.text_shadow(20, 45, ">>> WARP CONSOLE <<<", 10)
            self.text_shadow(20, 60, "PRESS NUMBER KEYS [1] TO [7]", 7); self.text_shadow(20, 70, "ON YOUR KEYBOARD TO", 7)
            self.text_shadow(20, 80, "DIRECTLY JUMP TO THAT LEVEL.", 7)
            self.text_shadow(20, 110, f"I) IMMORTAL: {self.immortal}", 7 if self.immortal else 5)
            self.text_shadow(20, 140, "DAMAGE MULTIPLIER", 7)
            mult = int(1 + (self.dev_dmg_slider * 99))
            self.text_shadow(20, 150, f"CURRENT: {mult}x", 10)
            pyxel.rect(20, 165, 100, 6, 1); pyxel.rect(21, 166, int(self.dev_dmg_slider * 98), 4, 8) 
            pyxel.rect(20 + int(self.dev_dmg_slider * 98) - 3, 162, 6, 12, 7) 
            
            self.text_shadow(20, 180, "CLICK SPELL TO UNLOCK:", 7)
            spells = ["Teleport", "Fireball", "Freeze", "Heal"]
            for i, sp in enumerate(spells):
                col = 10 if self.active_spell == sp else 5
                self.text_shadow(20 + i*45, 195, sp[:4], col)
            self.text_shadow(20, 220, "BACKSPACE TO EXIT", 6)
            
        elif self.state == 3:
            # Main Gameplay UI & Entity Drawing
            pyxel.rect(0, FLOOR_Y, SCREEN_WIDTH, 32, 4); pyxel.line(0, FLOOR_Y, SCREEN_WIDTH, FLOOR_Y, 9) 
            for i in range(0, SCREEN_WIDTH, 16):
                pyxel.line(i, FLOOR_Y + 4, i + 8, FLOOR_Y + 4, 0); pyxel.line(i + 8, FLOOR_Y + 8, i + 16, FLOOR_Y + 8, 0) 
            
            for p in getattr(self, 'platforms', []):
                if len(p) >= 8 and p[3] > 0: 
                    pyxel.rect(p[0] + 4, p[1] + 4, p[2], 8, 0); pyxel.rect(p[0], p[1], p[2], 8, 13)
                    pyxel.line(p[0], p[1], p[0] + p[2] - 1, p[1], 7) 
                    for i in range(p[0] + 4, p[0] + p[2] - 4, 8):
                        pyxel.line(i, p[1] + 2, i, p[1] + 6, 1); pyxel.line(i - 4, p[1] + 4, i + 4, p[1] + 4, 1)
                    pyxel.line(p[0] + 4, p[1] + 8, p[0] + 4, p[1] + 14, 3); pyxel.line(p[0] + p[2] - 8, p[1] + 8, p[0] + p[2] - 8, p[1] + 12, 11)

            for s in getattr(self, 'squires', []):
                if len(s) > 2: 
                    sx, sy = s[0], s[1]-12
                    pyxel.rect(sx - 6, sy, 12, 12, 0) 
                    sq_col = 12 if s[3] == 0 else (8 if s[3] == 1 else 10)
                    pyxel.rect(sx - 5, sy + 1, 10, 10, sq_col)
                    b_dir = s[4]
                    pyxel.rect(sx + (2 * b_dir), sy + 3, 2, 2, 7)
                    if s[3] == 0: pyxel.rect(sx + (6 * b_dir), sy - 4, 2, 10, 7)
                    elif s[3] == 1: pyxel.rect(sx + (4 * b_dir), sy + 6, 6, 2, 0)
                    elif s[3] == 2: 
                        shield_col = 9 if s[6] > 0 else 5
                        pyxel.rect(sx + (6 * b_dir), sy - 2, 2, 14, shield_col)
                        pyxel.rect(sx + (7 * b_dir), sy - 2, 2, 14, 10 if s[6] > 0 else 5)
                else: 
                    pyxel.rect(s[0] + 2, s[1] + 2, 12, 12, 0); pyxel.rect(s[0], s[1], 12, 12, 13); pyxel.rect(s[0] + 2, s[1] + 4, 8, 4, 8)

            for sk in getattr(self, 'skeletons', []):
                sx, sy = sk[0], sk[1]
                pyxel.rect(sx - 4, sy - 20, 8, 20, 0); pyxel.rect(sx - 3, sy - 19, 6, 18, 7); pyxel.circ(sx, sy - 22, 4, 7) 
                pyxel.pset(sx - 2, sy - 23, 0); pyxel.pset(sx + 2, sy - 23, 0)
                b_dir = sk[3]
                pyxel.line(sx, sy - 10, sx + (15 * b_dir), sy - 5, 5)

            for sp in getattr(self, 'earth_spikes', []):
                if sp[2] == 0: pyxel.line(sp[0] - 10, FLOOR_Y, sp[0] + 10, FLOOR_Y, 8 + (t%2)*2)
                else:
                    pyxel.tri(sp[0] - 12, FLOOR_Y, sp[0] + 12, FLOOR_Y, sp[0], FLOOR_Y - 40, 4) 
                    pyxel.tri(sp[0] - 6, FLOOR_Y, sp[0] + 6, FLOOR_Y, sp[0], FLOOR_Y - 35, 9)

            for b in getattr(self, 'bombs', []): 
                pyxel.circ(b[0], b[1], 4, 8); pyxel.circ(b[0], b[1], 2, 10) 
            for e in self.explosions: 
                pyxel.circb(e[0], e[1], e[2], e[4])
                if e[2] > 6: pyxel.circb(e[0], e[1], e[2] - 4, e[5]) 
            for p in self.particles: pyxel.pset(p[0], p[1], p[5])
            
            if self.level == 4 or self.level == 7:
                for w in self.geyser_warns: pyxel.rect(w[0] - 15, FLOOR_Y, 30, 4, 9 if self.level==4 else 8); pyxel.line(w[0] - 15, FLOOR_Y, w[0] + 15, FLOOR_Y, 10 if self.level==4 else 2)
                for g in self.geysers:
                    h = (30 - g[1]) * 10; pyxel.rect(g[0] - 12, FLOOR_Y - h, 24, h, 8) 
                    pyxel.rect(g[0] - 6, FLOOR_Y - h, 12, h, 9) 
                    pyxel.rect(g[0] - 2, FLOOR_Y - h, 4, h, 10); self.spawn_particles(g[0], FLOOR_Y - h, 9, 5, custom_vy=random.uniform(-8, -4), gravity=True)
            elif getattr(self, 'level', 1) == 5:
                for w in getattr(self, 'web_traps', []): 
                    pyxel.rect(w[0] - 24, w[1] - 3, 48, 6, 11); pyxel.rect(w[0] - 16, w[1] - 5, 32, 10, 3)
                    pyxel.circ(w[0] - 12, w[1] - 4, 4, 11); pyxel.circ(w[0] + 12, w[1] + 3, 3, 11)
                for ts in getattr(self, 'tiny_spiders', []):
                    sx, sy = ts[0], ts[1]
                    pyxel.circ(sx, sy, 2, 0); pyxel.pset(sx, sy - 1, 13); pyxel.pset(sx, sy, 8) 
                
            # Draw Core Entities
            self.draw_detailed_mage()
            self.draw_detailed_boss()
            
            for f in self.flames: pyxel.circ(f[0], f[1], max(2, f[4] // 5), 10 if f[4] > 35 else 9)
            
            for sh in getattr(self, 'soul_heads', []):
                pyxel.circ(sh[0], sh[1], 4, 13); pyxel.circ(sh[0], sh[1], 3, 7)
                pyxel.pset(sh[0] - 1, sh[1] - 1, 0); pyxel.pset(sh[0] + 1, sh[1] - 1, 0)
                self.draw_glow(sh[0] - sh[2]*2, sh[1] - sh[3]*2, 4, [0, 5, 13])
            
            # Draw Projectiles based on type
            for p in self.projectiles: 
                if len(p) > 4 and p[4] == "fireball":
                    pyxel.circ(p[0], p[1], 7, 8); pyxel.circ(p[0], p[1], 5, 9); pyxel.circ(p[0], p[1], 3, 10)
                    pyxel.circ(p[0] - p[2], p[1] - p[3], 5, 8); pyxel.circ(p[0] - p[2]*2, p[1] - p[3]*2, 3, 2); pyxel.circ(p[0], p[1], 1, 7)
                elif len(p) > 4 and p[4] == "freeze":
                    pyxel.circ(p[0], p[1], 3, 12); pyxel.pset(p[0], p[1], 7)
                else:
                    ex = p[0] - p[2]*2; ey = p[1] - p[3]*2
                    pyxel.line(p[0], p[1], ex, ey, 11); pyxel.line(p[0] - 1, p[1], ex - 1, ey, 3)
                    pyxel.line(p[0] + 1, p[1], ex + 1, ey, 3); pyxel.line(p[0], p[1] - 1, ex, ey - 1, 3)
                    pyxel.line(p[0], p[1] + 1, ex, ey + 1, 3); pyxel.circ(p[0], p[1], 2, 7)
                
            # Draw Boss Bullets based on type
            for b in self.boss_bullets: 
                if b[4] == "burger":
                    pyxel.circ(b[0], b[1], 5, 0); pyxel.rect(b[0] - 4, b[1] - 3, 8, 2, 4) 
                    pyxel.line(b[0] - 4, b[1] - 1, b[0] + 3, b[1] - 1, 3); pyxel.line(b[0] - 4, b[1], b[0] + 3, b[1], 8) 
                    pyxel.rect(b[0] - 4, b[1] + 1, 8, 2, 0); pyxel.rect(b[0] - 4, b[1] + 3, 8, 2, 4) 
                elif b[4] == "web": pyxel.circ(b[0], b[1], 4, 13); pyxel.circb(b[0], b[1], 3, 6)
                elif b[4] == "shotgun":
                    pyxel.circ(b[0], b[1], 2, 9); pyxel.circ(b[0] - b[2]*0.5, b[1] - b[3]*0.5, 1, 10); pyxel.pset(b[0] + 1, b[1] + 1, 8)
                elif b[4] == "witch_fireball":
                    pyxel.circ(b[0], b[1], 6, 2); pyxel.circ(b[0], b[1], 4, 8); pyxel.pset(b[0], b[1], 10)
                elif b[4] == "witch_freeze":
                    pyxel.circ(b[0], b[1], 4, 1); pyxel.circ(b[0], b[1], 2, 12)
                elif b[4] == "poison":
                    pyxel.circ(b[0], b[1], 8, 3); pyxel.circ(b[0], b[1], 6, 11); pyxel.circb(b[0], b[1], 8, 0)
                else: 
                    pyxel.line(b[0], b[1], b[0] - b[2]*1.2, b[1] - b[3]*1.2, 9); pyxel.rect(b[0] - 2, b[1] - 2, 4, 4, 10); pyxel.pset(b[0], b[1], 7) 
            
            # The Witch's Blindness mechanic (Level 7)
            if getattr(self, 'witch_darkness', False):
                cx, cy = int(self.px + 12), int(self.py + 16); r = 25 
                pyxel.rect(0, 0, SCREEN_WIDTH, cy - r, 0) 
                pyxel.rect(0, cy + r, SCREEN_WIDTH, SCREEN_HEIGHT - (cy + r), 0) 
                pyxel.rect(0, cy - r, cx - r, r * 2, 0) 
                pyxel.rect(cx + r, cy - r, SCREEN_WIDTH - (cx + r), r * 2, 0) 
                for i in range(r, r + 20): pyxel.circb(cx, cy, i, 0)
                    
            for ft in self.floating_texts: self.text_shadow(ft[0], ft[1], ft[2], ft[4])
            
            # --- OVERLAY UI (Health bars, etc) ---
            if self.level == 0:
                if getattr(self, 'tut_step', 0) == 0: self.text_shadow(SCREEN_WIDTH//2 - 40, 40, "MOVE USING W A S D", 7)
                elif self.tut_step == 1:
                    self.text_shadow(SCREEN_WIDTH//2 - 55, 40, "HOVER CURSOR OVER BOSS AND", 7)
                    self.text_shadow(SCREEN_WIDTH//2 - 65, 50, "HOLD LEFT MOUSE BUTTON TO SHOOT", 7)
                elif self.tut_step == 2:
                    self.text_shadow(SCREEN_WIDTH//2 - 80, 40, "YOU HAVE TEMPORARILY ACQUIRED FIREBALL.", 10)
                    self.text_shadow(SCREEN_WIDTH//2 - 40, 50, "PRESS Q TO SHOOT.", 10)
                elif self.tut_step == 3: self.text_shadow(SCREEN_WIDTH//2 - 20, 40, "WELL DONE!", 11)
            else:
                # Player HP
                pyxel.rect(10, 10, 80, 8, 1)
                pyxel.rect(12, 12, (max(0, self.p_hp_disp)/self.max_hp)*76, 4, 11 if not self.p_poisoned else 3)
                self.text_shadow(12, 20, f"HP: {int(self.p_hp)}/{self.max_hp}", 11 if not self.p_poisoned else 3)
                
                # Spell Cooldown Bar
                if self.active_spell:
                    cd_ratio = 0 if self.spell_cooldown <= 0 else (self.spell_cooldown / getattr(self, 'max_cd', 60))
                    pyxel.rect(10, 30, 80, 4, 1); pyxel.rect(12, 31, (1.0 - cd_ratio)*76, 2, 10 if cd_ratio == 0 else 5)
                    self.text_shadow(12, 36, f"SPELL: {self.active_spell}", 10 if cd_ratio == 0 else 5)

                # Boss HP / Mechanic Progress Bar
                pyxel.rect(SCREEN_WIDTH - 90, 10, 80, 8, 1)
                
                if getattr(self, 'level', 1) == 7 and getattr(self, 'boss_action', 0) == 1:
                    fill_ratio = min(1.0, self.skeletons_killed / 20.0)
                    pyxel.rect(SCREEN_WIDTH - 88, 12, fill_ratio * 76, 4, 7)
                    self.text_shadow(SCREEN_WIDTH - 88, 20, f"SKULLS: {self.skeletons_killed}/20", 7)
                else:
                    pyxel.rect(SCREEN_WIDTH - 88, 12, (max(0, self.boss_hp_disp)/self.boss_max_hp)*76, 4, 8 if getattr(self, 'level', 1) != 7 else 13)
                    
                    if getattr(self, 'level', 1) == 5 and getattr(self, 'boss_action', 0) == 1:
                        self.text_shadow(SCREEN_WIDTH - 88, 20, f"SURVIVE: {max(0, 20 - (self.swarm_timer//60))}s", 8)
                    elif getattr(self, 'level', 1) == 6 and (self.boss_hp / self.boss_max_hp) <= 0.2:
                        self.text_shadow(SCREEN_WIDTH - 88, 20, "ALPHA WOLF", 8)
                    else: 
                        self.text_shadow(SCREEN_WIDTH - 88, 20, f"PHASE {self.level}", 8 if getattr(self, 'level', 1) != 7 else 13)
            
        elif self.state == 4: self.text_shadow(100, 120, "GAME OVER", 8)
        elif self.state == 5: 
            if getattr(self, 'level', 1) == 7: self.text_shadow(SCREEN_WIDTH//2 - 40, 110, "WITCH SLAIN", 11)
            else: self.text_shadow(SCREEN_WIDTH//2 - 60, 110, "KNIGHT VANQUISHED", 11)
            self.text_shadow(SCREEN_WIDTH//2 - 75, 130, "PRESS SPACE TO CONTINUE", 7)

# Ignite the Engine
Game()