from pyxel import *

# ==========================================
# TIME ECHO LABYRINTH  v4.0
# 256 × 144  |  Sprites: 512×512 (16×16 px)
# ==========================================

WIDTH  = 256
HEIGHT = 144

GRAVITY   = 0.26
MAX_FALL  = 5.2
MOVE_SPD  = 0.95
JUMP_PWR  = -4.2

PW, PH = 8, 12
SPRITE_W = SPRITE_H = 16
SPR_OX, SPR_OY = -4, -4

BULLET_SPD  = 3.5
AREA_RADIUS = 32
AREA_CD     = 90

MAX_HEARTS   = 3   # Spieler-Leben

XP_PER_KILL  = {"ghost": 1, "crab": 2, "gargoyle": 4}
XP_THRESHOLDS = [0, 4, 8, 10, 15]
MAX_PLAYER_LEVEL = 5
AREA_RADIUS_PER_LEVEL = [0, 0, 32, 42, 52, 64]
SND_LEVELUP  = 18
SND_DEFLECT  = 19   # Kugel prallt ab

# ---- States ----
ST_TITLE      = 0
ST_PLAY       = 1
ST_DEAD       = 2
ST_WIN        = 3
ST_STORY      = 4
ST_INTRO      = 5
ST_TUTORIAL   = 6
ST_ENDING     = 7   # Ending-Animation: Held + Prinzessin draussen mit Konfetti

# ---- Sounds (Slots 10-29, damit pyxres 0-9 frei bleibt) ----
SND_SWITCH    = 10
SND_SHOOT     = 11  # Schuss-Sound
SND_HIT       = 12  # Monster-Tod (froehlisch)
SND_NEXT      = 13  # Naechstes Level
SND_JUMP      = 14
SND_AREA      = 15  # Flaechenangriff
SND_PORTAL    = 16
SND_CP        = 17
SND_LEVELUP   = 18
SND_DEFLECT   = 19  # Kugel prallt ab
SND_IMPACT    = 20  # Spieler wird getroffen (dominant)
SND_GAMEOVER  = 21  # Absteigende Game-Over-Melodie
SND_WIN       = 22  # Winning-Fanfare
SND_MUS_OUT   = 23  # Musik draussen (froehlich)
SND_MUS_DUN1  = 24  # Musik Dungeon Level 1 (leicht gruslig)
SND_MUS_DUN2  = 25  # Musik Dungeon Level 2 (gruseliger)
SND_MUS_DUN3  = 26  # Musik Dungeon Level 3 (sehr gruslig)
SND_MUS_BOSS  = 27  # Musik Boss Level 4 (sehr gruslig + intensiv)
SND_MUS_END   = 28  # Ending-Musik (froehlich)

# ---- Zwischen-Story ----
STORY_TEXTS = [
    ["Das Labyrinth pulsiert.",
     "Jedes Echo deiner Schritte",
     "hallt durch die Zeit.",
     "Du bist nicht allein hier."],
    ["Die Zeitschleife bebt.",
     "Deine Echos kaempfen mit.",
     "Ein letztes Echo bleibt."],
    ["Du spuerst seine Hitze.",
     "Der Drache bewacht Prinzessin Lyra.",
     "Nur dein staerkster Angriff",
     "kann ihn bezwingen."],
]

# ---- Intro-Szenen (vor Tutorial) ----
# Jede Szene: {"duration": frames, "type": "outside"/"dungeon"/"rescue"}
INTRO_SCENES = [
    {"duration": 180, "type": "outside"},   # Held auf Wiese
    {"duration": 160, "type": "walk_in"},   # Held geht in Dungeon
    {"duration": 220, "type": "dungeon"},   # Prinzessin + Drache
]

# ---- Tutorial-Aufnahme ----
# Jeder Schritt: Text anzeigen, dann Aktion ausfuehren
# "type": move_right / jump / shoot / stand / area
TUT_SCRIPT = [
    # Schritt 0: Text "Bewegen" → kurz warten → nach rechts laufen
    {"show_text": ["PFEILTASTEN / WASD: Bewegen", "Drueck RECHTS um zu laufen!"],
     "wait": 90, "type": "move_right", "duration": 70, "target_x": 75},
    # Schritt 1: Text "Springen" → warten → Sprung ueber Luecke
    {"show_text": ["LEERTASTE / W: Springen", "Ueberspringe die Luecke!"],
     "wait": 80, "type": "jump_and_move", "duration": 90, "target_x": 140},
    # Schritt 2: Text "Schiessen" → warten → Kugel abfeuern
    {"show_text": ["X: Schiessen – besieg die Krabbe!", ""],
     "wait": 80, "type": "shoot", "duration": 80, "target_x": 155},
    # Schritt 3: Text "Schalter" → warten → auf Schalter stellen
    {"show_text": ["Steh auf dem SCHALTER", "(oder dein Echo!)"],
     "wait": 80, "type": "move_to_switch", "duration": 80, "target_x": 178},
    # Schritt 4: Text "Flaechenangriff" → warten → C druecken
    {"show_text": ["C: FLAECHENANGRIFF  (Cooldown)", "Nur ab Spieler-Level 2!"],
     "wait": 80, "type": "area", "duration": 60},
    # Schritt 5: Abschluss
    {"show_text": ["Tutorial abgeschlossen!", "Betritt die Tuer zum Start."],
     "wait": 100, "type": "move_right", "duration": 120, "target_x": 230},
]


# ===========================================
#  HILFSFUNKTIONEN
# ===========================================

def rects_overlap(a, b):
    ax,ay,aw,ah = a
    bx,by,bw,bh = b
    return ax < bx+bw and ax+aw > bx and ay < by+bh and ay+ah > by


# ===========================================
#  KLASSEN
# ===========================================

class Particle:
    def __init__(self, x, y, col, vx=None, vy=None):
        self.x   = float(x)
        self.y   = float(y)
        self.col = col
        self.vx  = rndf(-0.5, 0.5) if vx is None else vx
        self.vy  = rndf(-1.0, 0.0) if vy is None else vy
        self.life = int(rndf(10, 22))

    def update(self):
        self.x   += self.vx
        self.y   += self.vy
        self.vy  += 0.06
        self.life -= 1

    def draw(self, cam_x):
        col = self.col if self.life > 8 else 5
        pset(int(self.x - cam_x), int(self.y), col)

    @property
    def dead(self):
        return self.life <= 0


class Bullet:
    def __init__(self, x, y, vx, col=10, owner="player"):
        self.x     = float(x)
        self.y     = float(y)
        self.vx    = vx
        self.col   = col
        self.owner = owner
        self.w, self.h = 5, 2

    def update(self, platforms, world_w):
        self.x += self.vx
        if self.x < -12 or self.x > world_w + 12:
            return False
        for r in platforms:
            if rects_overlap((self.x, self.y, self.w, self.h), r):
                return False
        return True

    def draw(self, cam_x):
        rect(int(self.x - cam_x), int(self.y), self.w, self.h, self.col)

    def rect(self):
        return (self.x, self.y, self.w, self.h)


class Enemy:
    W, H = 10, 10
    MAX_HP = 1          # Unterklassen ueberschreiben
    BULLET_IMMUNE = False

    def __init__(self, data):
        self.x     = float(data["x"])
        self.y     = float(data["y"])
        self.left  = data["left"]
        self.right = data["right"]
        self.vx    = float(data.get("vx", 0.6))
        self.vy    = 0.0
        self.on_ground = True
        self.anim  = 0
        self.alive = True
        self.hp    = data.get("hp", self.__class__.MAX_HP)
        self.hit_flash = 0   # weisses Aufblitzen wenn getroffen

    def hit_rect(self):
        return (self.x, self.y, self.W, self.H)

    def update(self, platforms, player_x, player_y, bullets_out):
        pass

    def draw(self, cam_x, anim_t):
        pass

    def _clamp(self):
        self.x = max(self.left, min(self.x, self.right - self.W))

    def _apply_gravity(self, platforms):
        self.vy += GRAVITY
        if self.vy > MAX_FALL:
            self.vy = MAX_FALL
        new_y = self.y + self.vy
        self.on_ground = False
        for r in platforms:
            rx,ry,rw,rh = r
            if rects_overlap((self.x, new_y, self.W, self.H), r):
                if self.vy > 0:
                    new_y = ry - self.H
                    self.on_ground = True
                else:
                    new_y = ry + rh
                self.vy = 0
                break
        self.y = new_y


class Crab(Enemy):
    MAX_HP    = 2
    COL_BODY  = 8
    COL_SHELL = 14
    COL_EYE   = 7

    def update(self, platforms, px, py, bullets_out):
        self.anim = (self.anim + 1) % 24
        self.x += self.vx
        if self.x <= self.left:
            self.x  = self.left;  self.vx = abs(self.vx)
        if self.x + self.W >= self.right:
            self.x  = self.right - self.W; self.vx = -abs(self.vx)

    def draw(self, cam_x, anim_t):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        f  = self.anim
        walk = f < 12
        if walk:
            line(sx+2, sy+7, sx,   sy+10, self.COL_BODY)
            line(sx+7, sy+7, sx+9, sy+10, self.COL_BODY)
        else:
            line(sx+2, sy+7, sx,   sy+9,  self.COL_BODY)
            line(sx+7, sy+7, sx+9, sy+9,  self.COL_BODY)
        if not walk:
            line(sx+3, sy+7, sx+1, sy+11, self.COL_SHELL)
            line(sx+6, sy+7, sx+8, sy+11, self.COL_SHELL)
        else:
            line(sx+3, sy+7, sx+1, sy+10, self.COL_SHELL)
            line(sx+6, sy+7, sx+8, sy+10, self.COL_SHELL)
        if walk:
            line(sx+4, sy+7, sx+3, sy+11, self.COL_BODY)
            line(sx+5, sy+7, sx+6, sy+11, self.COL_BODY)
        else:
            line(sx+4, sy+7, sx+2, sy+10, self.COL_BODY)
            line(sx+5, sy+7, sx+7, sy+10, self.COL_BODY)
        rect(sx+1, sy+2, 8, 6,  self.COL_SHELL)
        rectb(sx+1, sy+2, 8, 6, 7)
        pset(sx+3, sy+3, 7); pset(sx+5, sy+3, 7); pset(sx+7, sy+3, 7)
        line(sx+2, sy+6, sx+7, sy+6, self.COL_BODY)
        rect(sx+2, sy+3, 6, 3, self.COL_BODY)
        rectb(sx+1, sy+2, 8, 6, 7)
        pset(sx+3, sy+3, 7); pset(sx+5, sy+3, 7); pset(sx+7, sy+3, 7)
        sway = 1 if walk else 0
        if self.vx >= 0:
            line(sx+7, sy+2, sx+8+sway, sy,   7)
            rect(sx+8+sway, sy-2, 3, 2, self.COL_EYE)
            pset(sx+9+sway, sy-2, 0)
            line(sx+4, sy+2, sx+5, sy+1, 7)
            rect(sx+4, sy-1, 2, 2, self.COL_EYE)
        else:
            line(sx+2, sy+2, sx+1-sway, sy,   7)
            rect(sx-1-sway, sy-2, 3, 2, self.COL_EYE)
            pset(sx-sway, sy-2, 0)
            line(sx+5, sy+2, sx+4, sy+1, 7)
            rect(sx+3, sy-1, 2, 2, self.COL_EYE)
        open_px = 2 if f < 6 or (f >= 12 and f < 18) else 1
        rect(sx-3, sy+3,       3, 2,        self.COL_BODY)
        rect(sx-4, sy+1,       2, open_px,  self.COL_BODY)
        rect(sx-4, sy+3+open_px, 2, open_px, self.COL_SHELL)
        rectb(sx-4, sy+1, 2, open_px*2+1, 7)
        rect(sx+10, sy+3,     3, 2,         self.COL_BODY)
        rect(sx+11, sy+1,     2, open_px,   self.COL_BODY)
        rect(sx+11, sy+3+open_px, 2, open_px, self.COL_SHELL)
        rectb(sx+11, sy+1, 2, open_px*2+1, 7)
        # HP-Balken + Treffer-Flash
        if self.hit_flash > 0:
            rectb(sx, sy, self.W, self.H, 7)
            self.hit_flash -= 1
        draw_hp_bar(self.x, self.y, self.hp, self.MAX_HP, cam_x)


class Ghost(Enemy):
    MAX_HP   = 1
    COL_BODY = 6
    COL_GLOW = 12

    def __init__(self, data):
        super().__init__(data)
        self.float_t = rndf(0, 6.28)

    def update(self, platforms, px, py, bullets_out):
        self.anim    = (self.anim + 1) % 60
        self.float_t += 0.06
        self.y = float(self.y)
        dx = px - self.x
        if abs(dx) > 4:
            self.vx = 0.45 * (1 if dx > 0 else -1)
        else:
            self.vx = 0
        self.x += self.vx
        self._clamp()
        if not hasattr(self, "y_base"):
            self.y_base = self.y
        self.y = self.y_base + sin(self.float_t) * 3

    def draw(self, cam_x, anim_t):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        circb(sx+5, sy+5, 7, self.COL_GLOW)
        rect(sx+1, sy+1, 8, 7, self.COL_BODY)
        rect(sx, sy+3, 10, 4, self.COL_BODY)
        f = (anim_t // 6) % 3
        for i, ox in enumerate([sx+1, sx+3, sx+5, sx+7]):
            pset(ox, sy+9 + ((i+f) % 2), self.COL_BODY)
        rect(sx+2, sy+3, 2, 2, 7)
        rect(sx+6, sy+3, 2, 2, 7)
        pset(sx+2, sy+4, 0)
        pset(sx+6, sy+4, 0)
        rectb(sx+1, sy+1, 8, 7, 7)


class Gargoyle(Enemy):
    MAX_HP    = 3
    COL_BODY  = 2
    COL_WING  = 1
    COL_EYE   = 10

    def __init__(self, data):
        super().__init__(data)
        self.shoot_timer = data.get("shoot_timer", 80)
        self.facing = data.get("facing", 1)

    def update(self, platforms, px, py, bullets_out):
        self.anim = (self.anim + 1) % 60
        self.facing = 1 if px > self.x else -1
        self.shoot_timer -= 1
        if self.shoot_timer <= 0:
            bx = self.x + (12 if self.facing == 1 else -4)
            bullets_out.append(Bullet(bx, self.y+4,
                                      self.facing * 2.2, col=8, owner="enemy"))
            self.shoot_timer = 85
            # Kein Schuss-Sound beim Gargoyle (nur Impact zaehlt)

    def draw(self, cam_x, anim_t):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        f  = self.anim
        charge = self.shoot_timer < 20
        tail_dx = -3 if self.facing > 0 else 3
        line(sx+5, sy+10, sx+5+tail_dx,    sy+12, self.COL_BODY)
        line(sx+5+tail_dx, sy+12, sx+5+tail_dx*2, sy+11, self.COL_BODY)
        pset(sx+5+tail_dx*2, sy+10, 5)
        wing_y  = sy+2 + (1 if f % 20 < 10 else 0)
        rect(sx-5, wing_y,   6, 5, self.COL_WING)
        rectb(sx-5, wing_y,  6, 5, 5)
        line(sx-5, wing_y+1, sx-1, wing_y+3, 5)
        line(sx-5, wing_y+3, sx-1, wing_y+4, 5)
        rect(sx+9, wing_y,   6, 5, self.COL_WING)
        rectb(sx+9, wing_y,  6, 5, 5)
        line(sx+10, wing_y+1, sx+14, wing_y+3, 5)
        line(sx+10, wing_y+3, sx+14, wing_y+4, 5)
        rect(sx+1, sy+1,  8, 10, self.COL_BODY)
        rectb(sx+1, sy+1, 8, 10, 5)
        line(sx+2, sy+4, sx+7, sy+4, 5)
        line(sx+2, sy+6, sx+7, sy+6, 5)
        line(sx+2, sy+8, sx+7, sy+8, 5)
        line(sx+2, sy+1,  sx+1, sy-2, 7)
        line(sx+1, sy-2,  sx+2, sy-3, 7)
        pset(sx+2, sy-3, 5)
        line(sx+7, sy+1,  sx+8, sy-2, 7)
        line(sx+8, sy-2,  sx+7, sy-3, 7)
        pset(sx+7, sy-3, 5)
        mouth_open = 2 if charge else 1
        rect(sx+2, sy+6, 6, mouth_open+1, 0)
        pset(sx+3, sy+6, 7); pset(sx+5, sy+6, 7); pset(sx+7, sy+6, 7)
        if mouth_open > 1:
            pset(sx+4, sy+7, 7); pset(sx+6, sy+7, 7)
            rect(sx+3, sy+7, 3, 1, 8)
        ec = 8 if charge else self.COL_EYE
        if self.facing > 0:
            line(sx+2, sy+2, sx+3, sy+3, 7)
            line(sx+6, sy+3, sx+7, sy+2, 7)
        else:
            line(sx+2, sy+3, sx+3, sy+2, 7)
            line(sx+6, sy+2, sx+7, sy+3, 7)
        rect(sx+2, sy+3, 2, 2, ec)
        rect(sx+6, sy+3, 2, 2, ec)
        px_off = 1 if self.facing > 0 else 0
        pset(sx+2+px_off, sy+4, 0); pset(sx+6+px_off, sy+4, 0)
        if charge:
            pset(sx+4, sy+3, 7); pset(sx+4, sy+5, 7)
        rect(sx+2, sy+10,  2, 2, self.COL_BODY)
        rect(sx+6, sy+10,  2, 2, self.COL_BODY)
        pset(sx+1, sy+12, 5); pset(sx+3, sy+12, 5)
        pset(sx+5, sy+12, 5); pset(sx+7, sy+12, 5)
        if self.facing > 0:
            rect(sx+9, sy+5, 4, 2, 5)
            rectb(sx+9, sy+5, 4, 2, 7)
            if charge and (anim_t % 4 < 2):
                pset(sx+13, sy+5, 10)
                pset(sx+14, sy+6, 11)
        else:
            rect(sx-3, sy+5, 4, 2, 5)
            rectb(sx-3, sy+5, 4, 2, 7)
            if charge and (anim_t % 4 < 2):
                pset(sx-4, sy+5, 10)
                pset(sx-5, sy+6, 11)
        # HP-Balken + Treffer-Flash
        if self.hit_flash > 0:
            rectb(sx+1, sy+1, 8, 10, 7)
            self.hit_flash -= 1
        draw_hp_bar(self.x, self.y, self.hp, self.MAX_HP, cam_x, col=8)


# ══════════════════════════════════════════════
#  DRACHE – Boss Level 4
# ══════════════════════════════════════════════
class Dragon(Enemy):
    """
    Boss-Drache: nur durch Flächenangriff verwundbar (Kugeln prallen ab).
    Braucht Spieler-Level 5. HP=10, zwei Phasen.
    Phase 1 (hp>5): feuert langsam, bewegt sich gemächlich.
    Phase 2 (hp<=5): schneller, mehr Projektile, Feuerspur.
    """
    W, H          = 32, 28
    MAX_HP        = 10
    BULLET_IMMUNE = True   # Kugeln prallen ab

    def __init__(self, data):
        super().__init__(data)
        self.shoot_timer = 60
        self.facing      = data.get("facing", -1)
        self.phase       = 1
        self.float_t     = 0.0
        self.fire_trail  = []   # [(x,y,timer)] Feuerspur in Phase 2
        self.roar_timer  = 0    # kurzer Roar-Effekt bei Phasenwechsel
        self.defeated    = False

    @property
    def phase2(self):
        return self.hp <= self.MAX_HP // 2

    def update(self, platforms, px, py, bullets_out):
        self.anim      = (self.anim + 1) % 60
        self.float_t  += 0.04
        if self.hit_flash > 0: self.hit_flash -= 1

        # Phasenwechsel-Erkennung
        if self.phase == 1 and self.phase2:
            self.phase    = 2
            self.roar_timer = 60

        # Bewegung: schwebt langsam zum Spieler
        speed = 0.55 if not self.phase2 else 0.9
        dx = px - (self.x + self.W//2)
        dy = py - (self.y + self.H//2)
        if abs(dx) > 8:
            self.x += speed * (1 if dx > 0 else -1)
        if abs(dy) > 8:
            self.y += (speed * 0.5) * (1 if dy > 0 else -1)
        # Grenzen
        self.x = max(float(self.left),  min(self.x, float(self.right - self.W)))
        self.y = max(20.0, min(self.y, 100.0))
        self.facing = 1 if dx > 0 else -1

        # Schuss
        cd = 55 if not self.phase2 else 30
        self.shoot_timer -= 1
        if self.shoot_timer <= 0:
            self.shoot_timer = cd
            # Phase 1: 1 Projektil, Phase 2: 3 Fächer
            angles = [0] if not self.phase2 else [-0.3, 0, 0.3]
            for ang in angles:
                spd  = 2.0 if not self.phase2 else 2.8
                base = -1 if self.facing < 0 else 1
                vx2  = base * spd * cos(ang) - sin(ang) * spd * 0.3
                vy2  = spd * sin(ang)
                bx2  = self.x + (self.W if self.facing > 0 else -4)
                bullets_out.append(
                    Bullet(bx2, self.y + self.H//2, vx2, col=8, owner="enemy"))
                # zweites Feuerprojektil (groesser, col=9)
                bullets_out.append(
                    Bullet(bx2+2, self.y + self.H//2 + 2, vx2*0.9, col=9, owner="enemy"))

        # Feuerspur in Phase 2
        if self.phase2 and self.anim % 4 == 0:
            self.fire_trail.append([self.x + self.W//2,
                                    self.y + self.H - 4, 18])
        self.fire_trail = [[x, y, t-1] for x, y, t in self.fire_trail if t > 1]

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

    def draw(self, cam_x, anim_t):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        t  = anim_t
        p2 = self.phase2

        # Feuerspur
        for fx, fy, ft in self.fire_trail:
            col = 9 if ft > 9 else 8
            circb(int(fx - cam_x), int(fy), ft//4 + 1, col)

        # Roar-Effekt
        if self.roar_timer > 30:
            r2 = (60 - self.roar_timer) * 3
            circb(sx + self.W//2, sy + self.H//2, r2, 8)
            circb(sx + self.W//2, sy + self.H//2, r2+3, 9)

        # Schatten
        col_body = 2 if not p2 else 8   # Phase 2: dunkelrot
        col_wing = 1 if not p2 else 2
        col_eye  = 10 if not p2 else 8

        # Flügel (flattern)
        wing_off = 2 if (t // 8) % 2 == 0 else 0
        rect(sx-10, sy+4-wing_off,  12, 10, col_wing)
        rectb(sx-10, sy+4-wing_off, 12, 10, 5)
        line(sx-10, sy+5-wing_off, sx-1, sy+9-wing_off, 5)
        line(sx-10, sy+8-wing_off, sx-1, sy+12-wing_off, 5)
        rect(sx+self.W, sy+4-wing_off,  12, 10, col_wing)
        rectb(sx+self.W, sy+4-wing_off, 12, 10, 5)
        line(sx+self.W+1, sy+5-wing_off, sx+self.W+10, sy+9-wing_off, 5)
        line(sx+self.W+1, sy+8-wing_off, sx+self.W+10, sy+12-wing_off, 5)

        # Körper
        rect(sx+4, sy+6, self.W-8, self.H-8, col_body)
        rectb(sx+4, sy+6, self.W-8, self.H-8, 5)
        # Bauchschuppen
        for i in range(3):
            line(sx+6, sy+10+i*4, sx+self.W-6, sy+10+i*4, 5)

        # Schwanz
        line(sx+self.W-4, sy+self.H-4, sx+self.W+6, sy+self.H, col_body)
        line(sx+self.W+6, sy+self.H,   sx+self.W+8, sy+self.H-4, col_body)
        pset(sx+self.W+8, sy+self.H-5, 9)

        # Hals + Kopf
        rect(sx+10, sy, 12, 8, col_body)
        rectb(sx+10, sy, 12, 8, 5)
        # Kopf (schaut in facing-Richtung)
        hx = sx+2 if self.facing < 0 else sx+10
        rect(hx, sy-8, 18, 12, col_body)
        rectb(hx, sy-8, 18, 12, 5)
        # Hörner
        line(hx+2,  sy-8, hx+1,  sy-13, 11)
        pset(hx+1, sy-13, 9)
        line(hx+14, sy-8, hx+15, sy-13, 11)
        pset(hx+15, sy-13, 9)
        # Auge
        charge = self.shoot_timer < 15
        ec = 8 if charge else col_eye
        ex_off = 2 if self.facing < 0 else 12
        rect(hx+ex_off, sy-5, 4, 4, ec)
        pset(hx+ex_off+1, sy-4, 0)
        # Grimmige Augenbraue
        if self.facing < 0:
            line(hx+2, sy-8, hx+5, sy-6, 5)
        else:
            line(hx+12, sy-8, hx+15, sy-6, 5)
        # Maul
        mouth_h = 3 if charge else 2
        mx = hx if self.facing < 0 else hx+2
        rect(mx, sy+2, 8, mouth_h, 0)
        for i in range(0, 8, 2):
            pset(mx+i, sy+2, 7)
            if mouth_h > 2: pset(mx+i+1, sy+4, 7)
        # Feuer-Atem wenn laden
        if charge:
            for fi in range(4):
                fx2 = (hx-4-fi*7) if self.facing < 0 else (hx+18+fi*7)
                fc  = [8,9,10,7][fi]
                rect(fx2, sy, 6, 4, fc) if fi < 3 else rect(fx2, sy+1, 4, 2, fc)

        # HP-Balken (gross, über Drachen)
        bar_w = self.W + 10
        bsx   = sx - 5
        bsy   = sy - 14
        rect(bsx, bsy, bar_w, 4, 1)
        fill2 = int(self.hp / self.MAX_HP * bar_w)
        hcol  = 3 if not p2 else 8
        rect(bsx, bsy, fill2, 4, hcol)
        rectb(bsx, bsy, bar_w, 4, 5)
        text(bsx, bsy-7, "DRACHE", 8 if p2 else 3)

        # Treffer-Flash
        if self.hit_flash > 0:
            rectb(sx+4, sy+6, self.W-8, self.H-8, 7)

        # Phase-2-Aura
        if p2 and (t // 6) % 2 == 0:
            circb(sx + self.W//2, sy + self.H//2, 20, 8)


def make_enemy(data):
    t = data.get("type", "crab")
    if t == "ghost":    return Ghost(data)
    if t == "gargoyle": return Gargoyle(data)
    if t == "dragon":   return Dragon(data)
    return Crab(data)

def draw_hp_bar(ex, ey, hp, max_hp, cam_x, col=8):
    """Zeichnet HP-Balken ueber Gegner. Nur wenn hp < max_hp oder max_hp > 1."""
    if max_hp <= 1: return
    sx = int(ex - cam_x)
    sy = int(ey) - 5
    bw = 10
    rect(sx, sy, bw, 2, 1)
    fill = int(hp / max_hp * bw)
    rect(sx, sy, fill, 2, col)


# ===========================================
#  LEVEL-DATEN
# ===========================================

LEVELS = [
    # ---- TUTORIAL ----
    {
        "name": "TUTORIAL",
        "is_tutorial": True,
        "bg": 0, "bg2": 2, "wall_line": 1,
        "plat": 13, "plat_hi": 7, "plat_e": 2,
        "spike": 8, "tf": 9, "tg": 10, "tb": 4,
        "torches": [(60, 22), (190, 22)],
        "plants": [],
        "plant_col": 3, "plant_hi": 11, "pot_col": 4,
        "world_w": 256,
        "start": (10, 98),
        "door": (226, 80, 12, 24),
        "checkpoint": (128, 90, 10, 20),
        "portal": None,
        "platforms": [
            (0, 118, 256, 16),
            (0, 0, 8, 144), (248, 0, 8, 144), (0, 0, 256, 8),
            (14, 108, 55, 8),
            (90, 96, 54, 8),
            (155, 84, 80, 8),
        ],
        "spikes":   [(80, 112, 12, 6)],
        "switches": [(174, 76, 10, 8)],
        "enemies": [
            {"type": "crab", "x": 108, "y": 86, "w": 10, "h": 10,
             "left": 92, "right": 138, "vx": 0.5},
        ],
    },

    # ---- LEVEL 1 ----
    {
        "name": "LEVEL 1",
        "is_tutorial": False,
        "bg": 0, "bg2": 4, "wall_line": 9,
        "plat": 13, "plat_hi": 7, "plat_e": 9,
        "spike": 8, "tf": 9, "tg": 10, "tb": 4,
        "torches": [(52, 22), (190, 20), (308, 22)],
        "plants": [(90, 28), (220, 26)],
        "plant_col": 3, "plant_hi": 11, "pot_col": 4,
        "world_w": 420,
        "start": (18, 96),
        "door": (390, 82, 12, 24),
        "checkpoint": (188, 88, 10, 20),
        # Portal A am Anfang (x=26), Portal B landet direkt auf grosser Plattform
        "portal": {"ax": 340, "ay": 76, "bx": 138, "by": 10},
        "platforms": [
            (0, 118, 420, 16),
            (0, 0, 8, 144), (412, 0, 8, 144), (0, 0, 420, 8),
            # Boden-Plattformen (Hauptweg)
            (14, 110, 50, 8), (76, 98, 26, 8), (112, 110, 22, 8),
            (146, 100, 34, 8), (172, 84, 44, 8),
            (214, 110, 22, 8), (246, 98, 26, 8),
            (278, 74, 28, 8), (308, 98, 30, 8), (350, 84, 50, 8),
            # Grosse Arena-Plattform – sehr hoch oben (y=20)
            (118, 20, 100, 8),
        ],
        "spikes": [
            (64, 112, 14, 6), (136, 112, 14, 6),
            (232, 112, 14, 6), (270, 112, 12, 6),
        ],
        "switches": [(330, 76, 10, 8)],
        "enemies": [
            # Hauptweg-Gegner
            {"type": "crab",    "x": 80,  "y": 88,  "left": 76,  "right": 100, "vx": 0.55},
            # Ghost über Plattform y=84 → y_base soll 68 sein (über Plattform)
            {"type": "ghost",   "x": 180, "y": 68,  "left": 175, "right": 212, "vx": 0},
            # Arena-Plattform (y=20): Gegner bei y=10
            {"type": "crab",    "x": 125, "y": 10,  "left": 120, "right": 155, "vx": 0.6},
            {"type": "crab",    "x": 160, "y": 10,  "left": 155, "right": 185, "vx":-0.6},
            {"type": "ghost",   "x": 185, "y": 10,  "left": 120, "right": 216, "vx": 0},
            {"type": "gargoyle","x": 200, "y": 10,  "left": 120, "right": 216,
             "shoot_timer": 50, "facing": -1},
        ],
    },

    # ---- LEVEL 2 ----
    {
        "name": "LEVEL 2",
        "is_tutorial": False,
        "bg": 1, "bg2": 5, "wall_line": 1,
        "plat": 3, "plat_hi": 11, "plat_e": 7,
        "spike": 8, "tf": 9, "tg": 10, "tb": 4,
        "torches": [(34, 22), (138, 20), (262, 22), (382, 20)],
        "plants": [],
        "plant_col": 3, "plant_hi": 11, "pot_col": 5,
        "world_w": 440,
        "start": (18, 96),
        "door": (412, 40, 12, 24),
        "checkpoint": (210, 66, 10, 20),
        "portal": {"ax": 370, "ay": 56, "bx": 100, "by": 84},
        "platforms": [
            (0, 118, 440, 16),
            (432, 0, 8, 144), (0, 0, 440, 8),
            (60, 108, 28, 8), (100, 96, 32, 8), (146, 82, 26, 8),
            (186, 96, 26, 8), (222, 82, 26, 8), (258, 68, 26, 8),
            (294, 82, 26, 8), (330, 68, 24, 8), (362, 54, 24, 8),
            (404, 70, 18, 8),
            (158, 50, 104, 8),
        ],
        "spikes": [
            (50, 112, 12, 6), (90, 112, 12, 6),
            (144, 112, 14, 6), (216, 112, 12, 6), (284, 112, 14, 6),
        ],
        "switches": [(18, 112, 10, 8), (334, 62, 10, 8)],
        "enemies": [
            {"type": "crab",    "x": 108, "y": 86, "left": 102, "right": 128, "vx": 0.6},
            {"type": "ghost",   "x": 230, "y": 70, "left": 224, "right": 246, "vx": 0},
            {"type": "gargoyle","x": 300, "y": 72, "left": 294, "right": 318,
             "shoot_timer": 70, "facing": -1},
            {"type": "crab",    "x": 165, "y": 40, "left": 160, "right": 196, "vx": 0.65},
            {"type": "ghost",   "x": 196, "y": 36, "left": 160, "right": 258, "vx": 0},
            {"type": "ghost",   "x": 228, "y": 36, "left": 160, "right": 258, "vx": 0},
            {"type": "gargoyle","x": 244, "y": 40, "left": 160, "right": 260,
             "shoot_timer": 40, "facing": -1},
        ],
    },

    # ---- LEVEL 3 ----
    {
        "name": "LEVEL 3",
        "is_tutorial": False,
        "bg": 0, "bg2": 2, "wall_line": 2,
        "plat": 13, "plat_hi": 7, "plat_e": 10,
        "spike": 10, "tf": 9, "tg": 10, "tb": 4,
        "torches": [(40, 22), (195, 20), (370, 22), (445, 20)],
        "plants": [],
        "plant_col": 3, "plant_hi": 11, "pot_col": 5,
        "world_w": 500,
        "start": (22, 60),
        "door": (466, 30, 12, 24),
        "checkpoint": (270, 72, 10, 20),
        "portal": {"ax": 50, "ay": 90, "bx": 380, "by": 56},
        "platforms": [
            (0, 118, 500, 16),
            (0, 0, 8, 144), (492, 0, 8, 144), (0, 0, 500, 8),
            (14, 68, 24, 8), (50, 84, 24, 8), (88, 100, 22, 8),
            (118, 84, 24, 8), (152, 68, 24, 8), (186, 52, 24, 8),
            (222, 68, 24, 8), (256, 84, 24, 8), (290, 100, 24, 8),
            (324, 84, 24, 8), (358, 68, 24, 8), (392, 52, 24, 8),
            (458, 60, 22, 8),
            (194, 28, 124, 8),
        ],
        "spikes": [
            (38, 112, 12, 6), (74, 112, 12, 6), (108, 112, 12, 6),
            (142, 112, 10, 6), (208, 112, 14, 6),
            (316, 112, 12, 6), (380, 112, 10, 6),
        ],
        "switches": [(22, 60, 10, 8), (226, 60, 10, 8), (462, 44, 10, 8)],
        "enemies": [
            {"type": "crab",    "x": 122, "y": 74, "left": 120, "right": 138, "vx": 0.7},
            {"type": "ghost",   "x": 260, "y": 72, "left": 256, "right": 278, "vx": 0},
            {"type": "gargoyle","x": 396, "y": 42, "left": 392, "right": 414,
             "shoot_timer": 60, "facing": -1},
            {"type": "crab",    "x": 202, "y": 18, "left": 196, "right": 234, "vx": 0.75},
            {"type": "crab",    "x": 258, "y": 18, "left": 248, "right": 314, "vx":-0.75},
            {"type": "ghost",   "x": 240, "y": 14, "left": 196, "right": 316, "vx": 0},
            {"type": "ghost",   "x": 282, "y": 14, "left": 196, "right": 316, "vx": 0},
            {"type": "gargoyle","x": 306, "y": 18, "left": 196, "right": 316,
             "shoot_timer": 45, "facing": -1},
        ],
    },

    # ---- LEVEL 4: DRACHEN-BOSS ----
    {
        "name": "LEVEL 4 - BOSS",
        "is_tutorial": False,
        "bg": 0, "bg2": 1, "wall_line": 2,
        "plat": 2, "plat_hi": 5, "plat_e": 1,
        "spike": 8, "tf": 8, "tg": 9, "tb": 1,
        "torches": [(40, 22), (280, 22), (160, 22)],
        "plants": [],
        "plant_col": 8, "plant_hi": 9, "pot_col": 2,
        "world_w": 340,
        "start": (18, 96),
        "door": (312, 80, 12, 24),
        "checkpoint": (168, 88, 10, 20),
        "portal": None,
        "platforms": [
            (0, 118, 340, 16),
            (0, 0, 8, 144), (332, 0, 8, 144), (0, 0, 340, 8),
            # Plattformen links
            (14, 108, 60, 8),
            (88, 96, 40, 8),
            # Mitte (Kampfarena, breit)
            (130, 84, 80, 8),
            # Plattformen rechts
            (224, 96, 40, 8),
            (278, 108, 50, 8),
            # Erhöhte Plattform für Tür
            (298, 70, 30, 8),
        ],
        "spikes": [
            (70, 112, 20, 6),
            (200, 112, 22, 6),
        ],
        "switches": [(308, 62, 10, 8)],
        "enemies": [
            # Wächter vor dem Boss
            {"type": "crab",    "x": 90,  "y": 86, "left": 88,  "right": 126, "vx": 0.7},
            {"type": "gargoyle","x": 224, "y": 86, "left": 224, "right": 262,
             "shoot_timer": 70, "facing": -1},
            # DER DRACHE – schwebt über der Mitte
            {"type": "dragon",  "x": 148, "y": 50, "left": 60, "right": 280,
             "vx": 0, "hp": 20},
        ],
    },
]


# ===========================================
#  HAUPTSPIEL-KLASSE
# ===========================================

class Game:
    def __init__(self):
        self.px = 0.0; self.py = 0.0
        self.vx = 0.0; self.vy = 0.0
        self.on_ground = False
        self.facing = 1

        self.level_idx = 0
        self.level = LEVELS[0]
        self.cam_x = 0.0

        self.state = ST_TITLE
        self.door_open = False
        self.sw_active = []
        self.checkpoint_active = False
        self.checkpoint_pos = (0, 0)

        self.run_frames = []
        self.clones = []

        self.enemies  = []
        self.bullets  = []
        self.particles = []

        self.xp            = 0
        self.player_level  = 1
        self.xp_for_next   = XP_THRESHOLDS[1]
        self.levelup_flash = 0

        self.area_cd    = 0
        self.area_effect = None
        self.area_warning = None

        self.portal_timer = 0
        self.portal_data  = None

        self.anim_t    = 0
        self.flash     = 0
        self.hearts    = MAX_HEARTS   # aktuelle Leben
        self.dead_timer = 0
        self.ending_timer = 0         # Ending-Animation Timer
        self.story_timer = 0
        self.story_lines = []

        # Intro-Animation
        self.intro_scene   = 0       # welche Szene
        self.intro_timer   = 0       # Timer fuer aktuelle Szene
        self.intro_hero_x  = 20.0   # Held-X in Intro-Szene
        self.intro_hero_y  = 100.0
        self.intro_hero_vx = 0.0
        self.intro_hero_vy = 0.0
        self.intro_on_g    = True

        # Tutorial-Playback
        self.tut_step       = 0      # aktueller Skript-Schritt
        self.tut_phase      = "text" # "text" → Text zeigen und warten; "action" → Aktion ausfuehren
        self.tut_phase_timer = 0
        self.tut_action_t   = 0      # Frame-Zaehler innerhalb Aktion
        self.tut_hint       = ("", "")
        self.tut_hint_t     = 0
        self.tut_jump_triggered = False
        self.tut_shoot_done  = False
        self.tut_area_done   = False

        self._setup_sounds()
        self.load_level(0, reset_clones=True)

    # ---- Sounds ----
    def _setup_sounds(self):
        # Gameplay-Sounds
        sound(SND_SWITCH).set("c3 e3", "p", "5", "n", 8)
        # Schuss: kurzes "pew" (aufsteigend)
        sound(SND_SHOOT).set("c2 e2 g2", "p", "765", "n", 5)
        # Monster-Tod: froehlicher kurzer Dreiklang
        sound(SND_HIT).set("c3 e3 g3", "t", "777", "n", 8)
        # Naechstes Level: aufsteigender Fanfare-Lauf
        sound(SND_NEXT).set("c3 e3 g3 c4 e4", "s", "77777", "n", 12)
        # Sprung
        sound(SND_JUMP).set("e2 g2", "p", "65", "n", 5)
        # Flaechenangriff: tiefer Boom-Sweep
        sound(SND_AREA).set("c1 c1 f1 c2", "n", "7777", "n", 10)
        # Portal: mystischer Sweep
        sound(SND_PORTAL).set("c3 g3 c4 g3 c4", "s", "76543", "n", 12)
        # Checkpoint
        sound(SND_CP).set("c3 g3 c4", "t", "677", "n", 10)
        # Level-Up Fanfare
        sound(SND_LEVELUP).set("c3 e3 g3 e4 c4 e4", "s", "777777", "n", 15)
        # Abprall: metallisches "ting"
        sound(SND_DEFLECT).set("b3 f3", "p", "74", "n", 5)
        # Spieler getroffen: dominanter tiefer Impact
        sound(SND_IMPACT).set("c1 a0 f0", "n", "777", "n", 12)
        # Game Over: absteigende Melodie (klassisch)
        sound(SND_GAMEOVER).set("c3 b2 a2 g2 f2 e2 d2 c2", "s", "77777777", "n", 18)
        # Winning Fanfare: festlich aufsteigend
        sound(SND_WIN).set("c3 e3 g3 c4 e4 c4 e4 c4", "t", "77777777", "n", 15)
        # Musik draussen: froehliche Melodie (C-Dur)
        sound(SND_MUS_OUT).set("c3 e3 g3 e3 f3 a3 g3 e3 c3 e3 g3 c4", "t",
                               "444444444444", "n", 30)
        # Musik Dungeon 1: etwas duester, Moll
        sound(SND_MUS_DUN1).set("a2 c3 e3 c3 a2 g2 a2 c3", "s",
                                "44444444", "n", 35)
        # Musik Dungeon 2: gruselier, chromatisch
        sound(SND_MUS_DUN2).set("a2 a2 g2 f2 e2 f2 g2 a2", "s",
                                "44444444", "n", 40)
        # Musik Dungeon 3: sehr gruselig, tief
        sound(SND_MUS_DUN3).set("a1 e2 d2 c2 b1 c2 d2 e2", "n",
                                "44444444", "n", 45)
        # Musik Boss: intensiv und dunkel
        sound(SND_MUS_BOSS).set("a1 a1 e2 a1 d2 a1 c2 b1", "n",
                                "77777777", "n", 25)
        # Ending-Musik: froehlich, triumphierend
        sound(SND_MUS_END).set("c3 e3 g3 c4 b3 g3 e3 c3 e3 g3 c4 c4", "t",
                               "444444444444", "n", 28)

    # ---- Level laden ----
    def load_level(self, idx, reset_clones=True):
        self.level_idx = idx
        self.level = LEVELS[idx]
        # Musik stoppen wenn nicht Level 4 (Level 4 startet eigene Musik)
        if idx != 4:
            stop(1)
        lv = self.level

        sx, sy = lv["start"]
        self.px, self.py = float(sx), float(sy)
        self.vx = self.vy = 0.0
        self.on_ground = False
        self.facing = 1
        self.cam_x = 0.0

        self.door_open = False
        self.sw_active = [False] * len(lv["switches"])
        self.checkpoint_active = False
        self.checkpoint_pos = lv["start"]

        self.run_frames = []
        self.flash = 0
        self.area_cd = 0
        self.area_effect = None
        self.area_warning = None
        self.portal_timer = 0
        self.portal_data = lv.get("portal")
        self.bullets = []
        self.particles = []
        self.levelup_flash = 0

        if reset_clones:
            self.clones.clear()

        self.enemies = [make_enemy(d) for d in lv["enemies"]]
        # Musik stoppen beim Level-Wechsel (nur Level 4 hat Musik)
        if idx != 4:
            try: stop(1)
            except: pass

    def restart_checkpoint(self):
        if self.run_frames:
            self.clones.append({
                "frames": self.run_frames[:],
                "idx": 0, "alive": True,
                "locked": False,
                "x": self.run_frames[-1]["x"],
                "y": self.run_frames[-1]["y"],
                "facing": self.run_frames[-1]["facing"],
            })

        cx, cy = self.checkpoint_pos
        self.px, self.py = float(cx), float(cy)
        self.vx = self.vy = 0.0
        self.on_ground = False
        self.facing = 1
        self.state = ST_PLAY
        self.dead_timer = 0
        self.run_frames = []
        self.flash = 30
        self.bullets = []
        self.area_effect = None
        self.enemies = [make_enemy(d) for d in self.level["enemies"]]
        # Herz wird beim Treffer abgezogen, nicht hier zurückgesetzt

    # ---- Kollision ----
    def solid_at(self, x, y, w, h):
        for r in self.level["platforms"]:
            if rects_overlap((x, y, w, h), r):
                return r
        return None

    def spike_hit(self, x, y, w, h):
        foot = (x+1, y+h-3, w-2, 3)
        for sx, sy, sw, sh in self.level["spikes"]:
            if rects_overlap(foot, (sx+1, sy, sw-2, 3)):
                return True
        return False

    def move_actor(self, ax, ay, avx, avy, aw, ah, on_g):
        avy += GRAVITY
        if avy > MAX_FALL: avy = MAX_FALL
        nx = ax + avx
        hit = self.solid_at(nx, ay, aw, ah)
        if hit:
            stepped = False
            if on_g and avx != 0:
                for s in range(1, 9):
                    if not self.solid_at(nx, ay-s, aw, ah):
                        ax, ay, stepped = nx, ay-s, True; break
            if not stepped:
                nx  = hit[0]-aw if avx > 0 else hit[0]+hit[2]
                avx = 0
            else:
                nx = ax
        ax = nx
        ny = ay + avy
        on_g2 = False
        hit = self.solid_at(ax, ny, aw, ah)
        if hit:
            if avy > 0:
                ny, on_g2 = hit[1]-ah, True
            else:
                ny = hit[1]+hit[3]
            avy = 0
        ay = ny
        return ax, ay, avx, avy, on_g2

    # ==========================================
    #  UPDATE HAUPT-DISPATCHER
    # ==========================================
    def update(self):
        self.anim_t += 1
        if self.flash > 0: self.flash -= 1
        if self.area_cd > 0: self.area_cd -= 1
        if self.portal_timer > 0: self.portal_timer -= 1
        if self.levelup_flash > 0: self.levelup_flash -= 1

        if self.state == ST_TITLE:
            self._update_title()
        elif self.state == ST_INTRO:
            self._update_intro()
        elif self.state == ST_TUTORIAL:
            self._update_tutorial_playback()
        elif self.state == ST_PLAY:
            self._update_play()
            self._update_music()
        elif self.state == ST_DEAD:
            self._update_dead()
        elif self.state == ST_WIN:
            self._update_win()
        elif self.state == ST_ENDING:
            self._update_ending()
        elif self.state == ST_STORY:
            self._update_story_screen()

    def _update_title(self):
        # Titel: zwei Optionen – Tutorial oder Skip
        if btnp(KEY_T):
            # Tutorial starten (Intro-Animation → Tutorial-Playback)
            self._start_intro()
        elif btnp(KEY_SPACE) or btnp(KEY_RETURN) or btnp(KEY_S):
            # Direkt in Level 1
            self.xp = 0
            self.player_level = 1
            self.xp_for_next  = XP_THRESHOLDS[1]
            self.load_level(1, reset_clones=True)
            self.state = ST_PLAY

    def _start_intro(self):
        self.intro_scene  = 0
        self.intro_timer  = INTRO_SCENES[0]["duration"]
        self.intro_hero_x = 20.0
        self.intro_hero_y = 100.0
        self.intro_hero_vx = 0.0
        self.intro_hero_vy = 0.0
        self.intro_on_g   = True
        self.state = ST_INTRO
        play(1, SND_MUS_OUT, loop=True)   # Froehliche Aussen-Musik

    def _update_intro(self):
        self.intro_timer -= 1
        scene = INTRO_SCENES[self.intro_scene]
        # Held-Bewegung in Aussen-Szene
        if scene["type"] == "outside":
            # Held laeuft langsam nach rechts
            self.intro_hero_x += 0.5
        elif scene["type"] == "walk_in":
            # Held laeuft schnell nach rechts in Dungeon-Eingang
            self.intro_hero_x += 1.0
        # In Dungeon-Szene bewegt sich der Held nicht (Standbild)

        # Weiter zur naechsten Szene oder Tutorial starten
        skip = btnp(KEY_SPACE) or btnp(KEY_RETURN) or btnp(KEY_ESCAPE)
        if self.intro_timer <= 0 or skip:
            self.intro_scene += 1
            if self.intro_scene >= len(INTRO_SCENES) or skip:
                # Intro fertig: Tutorial-Playback starten
                self._start_tutorial_playback()
            else:
                self.intro_timer = INTRO_SCENES[self.intro_scene]["duration"]
                self.intro_hero_x = 20.0

    def _start_tutorial_playback(self):
        self.xp = 0
        self.player_level = 1
        self.xp_for_next  = XP_THRESHOLDS[1]
        self.load_level(0, reset_clones=True)
        play(1, SND_MUS_OUT, loop=True)   # Musik falls Intro uebersprungen
        self.tut_step        = 0
        self.tut_phase       = "text"
        self.tut_phase_timer = TUT_SCRIPT[0]["wait"]
        step = TUT_SCRIPT[0]
        self.tut_hint        = (step["show_text"][0], step["show_text"][1])
        self.tut_hint_t      = step["wait"]
        self.tut_action_t    = 0
        self.tut_jump_triggered = False
        self.tut_shoot_done  = False
        self.tut_area_done   = False
        self.state = ST_TUTORIAL

    def _update_tutorial_playback(self):
        """Spielt das Tutorial als scripted Sequenz ab."""
        self._update_enemies()
        self._update_bullets()
        self._update_area()
        self._update_particles()
        self._update_camera()

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

        if self.tut_step >= len(TUT_SCRIPT):
            # Tutorial abgeschlossen → Level 1 starten
            self.load_level(1, reset_clones=True)
            self.state = ST_PLAY
            return

        step = TUT_SCRIPT[self.tut_step]

        if self.tut_phase == "text":
            self.tut_phase_timer -= 1
            # Text anzeigen
            self.tut_hint   = (step["show_text"][0], step["show_text"][1])
            self.tut_hint_t = self.tut_phase_timer + 10
            if self.tut_phase_timer <= 0 or btnp(KEY_SPACE) or btnp(KEY_RETURN):
                # Uebergang in Aktion
                self.tut_phase       = "action"
                self.tut_action_t    = 0
                self.tut_jump_triggered = False
                self.tut_shoot_done  = False
                self.tut_area_done   = False
        else:
            # Aktion ausfuehren
            self.tut_action_t += 1
            self._run_tut_action(step)

            # Aktion abgeschlossen?
            if self.tut_action_t >= step["duration"]:
                self.tut_step   += 1
                self.tut_phase   = "text"
                if self.tut_step < len(TUT_SCRIPT):
                    nxt = TUT_SCRIPT[self.tut_step]
                    self.tut_phase_timer = nxt["wait"]
                    self.tut_hint        = (nxt["show_text"][0], nxt["show_text"][1])
                    self.tut_hint_t      = nxt["wait"] + 10

        # Immer Physik und Partikel aktuell halten
        self._sim_player_physics(0.0, self.vy)
        self._update_switches()

    def _run_tut_action(self, step):
        """Simuliert Spieler-Input basierend auf Skript-Schritt."""
        t = self.tut_action_t
        stype = step["type"]

        if stype == "move_right":
            target = step.get("target_x", self.px + 50)
            if self.px < target - 2:
                self._sim_player_physics(MOVE_SPD, self.vy)
                self.facing = 1
            else:
                self._sim_player_physics(0.0, self.vy)

        elif stype == "jump_and_move":
            target = step.get("target_x", self.px + 60)
            mvx = MOVE_SPD if self.px < target - 2 else 0.0
            if not self.tut_jump_triggered and self.on_ground and self.px > 60:
                self.vy = JUMP_PWR
                play(0, SND_JUMP)
                self.tut_jump_triggered = True
            self._sim_player_physics(mvx, self.vy)
            self.facing = 1

        elif stype == "shoot":
            target = step.get("target_x", self.px + 30)
            if self.px < target - 2:
                self._sim_player_physics(MOVE_SPD, self.vy)
                self.facing = 1
            else:
                self._sim_player_physics(0.0, self.vy)
            # Schuss nach 20 Frames
            if t == 20 and not self.tut_shoot_done:
                sx2 = self.px + 10
                self.bullets.append(Bullet(sx2, self.py+4,
                                           self.facing*BULLET_SPD, col=10, owner="player"))
                play(0, SND_SHOOT)
                self.tut_shoot_done = True

        elif stype == "move_to_switch":
            target = step.get("target_x", 178)
            if self.px < target - 2:
                self._sim_player_physics(MOVE_SPD, self.vy)
                self.facing = 1
            else:
                self._sim_player_physics(0.0, self.vy)

        elif stype == "area":
            self._sim_player_physics(0.0, self.vy)
            if t == 15 and not self.tut_area_done:
                if self._area_unlocked() and self.area_cd == 0:
                    self._trigger_area(self.px+PW//2, self.py+PH//2)
                    self.area_cd = AREA_CD
                    play(0, SND_AREA)
                self.tut_area_done = True

        else:
            # Fallback: nichts tun
            self._sim_player_physics(0.0, self.vy)

    def _sim_player_physics(self, avx, avy):
        """Physik fuer den Spieler ohne echten Input – wird im Tutorial-Playback benutzt."""
        nx, ny, nvx, nvy, og = self.move_actor(
            self.px, self.py, avx, avy, PW, PH, self.on_ground)
        self.px, self.py = nx, ny
        self.vx, self.vy = nvx, nvy
        self.on_ground = og
        self.run_frames.append({"x": self.px, "y": self.py, "facing": self.facing})
        if len(self.run_frames) > 2000:
            self.run_frames.pop(0)

    def _update_play(self):
        if btnp(KEY_R):
            self.xp = 0
            self.player_level = 1
            self.xp_for_next  = XP_THRESHOLDS[1]
            self.load_level(self.level_idx, reset_clones=True)

        self._update_player()
        self._update_clones()
        self._update_enemies()
        self._update_bullets()
        self._update_area()
        self._update_particles()
        self._update_portal()
        self._check_checkpoint()
        self._update_switches()
        self._check_hazards()
        self._check_door()
        self._update_camera()

    def _update_dead(self):
        self.dead_timer -= 1
        if self.dead_timer <= 0:
            self.hearts = MAX_HEARTS
            self.restart_checkpoint()

    def _update_win(self):
        if btnp(KEY_R):
            self.xp = 0
            self.player_level = 1
            self.xp_for_next  = XP_THRESHOLDS[1]
            self.hearts = MAX_HEARTS
            self.load_level(0, reset_clones=True)
            self.state = ST_TITLE

    def _update_ending(self):
        self.ending_timer += 1
        if self.ending_timer > 600 or btnp(KEY_SPACE) or btnp(KEY_RETURN):
            self.state = ST_WIN

    def _update_story_screen(self):
        self.story_timer -= 1
        if self.story_timer <= 0 or btnp(KEY_SPACE) or btnp(KEY_RETURN):
            self._advance_level()

    def _update_player(self):
        avx = 0.0
        if btn(KEY_LEFT) or btn(KEY_A):  avx = -MOVE_SPD; self.facing = -1
        if btn(KEY_RIGHT) or btn(KEY_D): avx =  MOVE_SPD; self.facing =  1

        avy = self.vy
        if self.on_ground and (btnp(KEY_SPACE) or btnp(KEY_UP) or btnp(KEY_W)):
            avy = JUMP_PWR
            play(0, SND_JUMP)

        if btnp(KEY_X):
            sx = self.px + (10 if self.facing == 1 else -6)
            self.bullets.append(Bullet(sx, self.py+4,
                                       self.facing*BULLET_SPD, col=10, owner="player"))
            play(0, SND_SHOOT)

        if btnp(KEY_C) or btnp(KEY_Z):
            if self._area_unlocked() and self.area_cd == 0:
                self._trigger_area(self.px+PW//2, self.py+PH//2)
                self.area_cd = AREA_CD
                play(0, SND_AREA)

        nx, ny, nvx, nvy, og = self.move_actor(
            self.px, self.py, avx, avy, PW, PH, self.on_ground)
        self.px, self.py = nx, ny
        self.vx, self.vy = nvx, nvy
        self.on_ground = og

        self.run_frames.append({"x": self.px, "y": self.py, "facing": self.facing})
        if len(self.run_frames) > 2000:
            self.run_frames.pop(0)

    def _update_clones(self):
        for c in self.clones:
            if not c["alive"]: continue
            if c.get("locked"):
                if self.anim_t % 7 == 0:
                    self._spawn_p(c["x"], c["y"]+PH//2, 12, 1)
                continue
            if c["idx"] >= len(c["frames"]):
                c["alive"] = False; continue
            f = c["frames"][c["idx"]]
            c["x"], c["y"], c["facing"] = f["x"], f["y"], f["facing"]
            if c["idx"] % 5 == 0:
                self._spawn_p(c["x"], c["y"]+PH//2, 12, 1)
            for sw in self.level["switches"]:
                if rects_overlap((c["x"],c["y"],PW,PH),
                                 (sw[0],sw[1]-2,sw[2],sw[3]+3)):
                    c["locked"] = True
                    break
            c["idx"] += 1

    def _update_enemies(self):
        new_bullets = []
        for e in self.enemies:
            e.update(self.level["platforms"], self.px, self.py, new_bullets)
        self.bullets += new_bullets

    def _update_bullets(self):
        new_b = []
        for b in self.bullets:
            alive = b.update(self.level["platforms"], self.level["world_w"])
            if not alive:
                self._spawn_p(b.x, b.y, 10, 3)
                continue
            if b.owner == "player":
                hit = None
                for e in self.enemies:
                    if rects_overlap(b.rect(), e.hit_rect()):
                        hit = e; break
                if hit:
                    if hit.BULLET_IMMUNE:
                        # Kugel prallt ab: Partikel + Sound, Kugel weg
                        for _ in range(4):
                            self._spawn_p(b.x, b.y, 7)
                        play(0, SND_DEFLECT)
                        # Kleiner Hinweis-Text
                        self.tut_hint   = ("KUGELN WIRKEN NICHT!", "Benutze C: Flaechenangriff")
                        self.tut_hint_t = 90
                        continue   # Kugel verschwindet, Gegner unverletzt
                    hit.hp -= 1
                    hit.hit_flash = 8
                    if hit.hp <= 0:
                        for _ in range(5):
                            self._spawn_p(hit.x+5, hit.y+5, 8)
                        self._grant_xp(hit)
                        self.enemies.remove(hit)
                        play(0, SND_HIT)   # froehlicher Tod-Sound
                    else:
                        play(0, SND_DEFLECT)   # Treffer aber nicht tot
                    continue
            new_b.append(b)
        self.bullets = new_b

    def _trigger_area(self, cx, cy):
        r = self._current_area_radius()
        self.area_effect = {"cx": cx, "cy": cy, "radius": r,
                            "timer": 22, "max": 22}
        killed = []
        for e in self.enemies:
            ex = e.x + e.W/2; ey = e.y + e.H/2
            dx = ex-cx; dy = ey-cy
            if dx*dx+dy*dy <= r**2:
                # Flächenangriff-Schaden skaliert mit Spieler-Level
                # PLV2=1.5, PLV3=2, PLV4=2.5, PLV5=3 (bei .5 wird aufgerundet)
                area_dmg_table = {2: 1.5, 3: 2.0, 4: 2.5, 5: 3.0}
                base_dmg = area_dmg_table.get(self.player_level, 1.0)
                # ceil(.5) = 1 runde auf ganze Zahl auf
                dmg = int(base_dmg + 0.5)   # aufrunden bei .5
                e.hp -= dmg
                e.hit_flash = 12
                for _ in range(6):
                    self._spawn_p(e.x + e.W//2, e.y + e.H//2, 8)
                if e.hp <= 0:
                    killed.append(e)
        for e in killed:
            self._grant_xp(e)
            self.enemies.remove(e)
            play(0, SND_HIT)   # froehlicher Tod-Sound
        for i in range(16):
            ang = i / 16 * 6.283
            self.particles.append(Particle(
                cx + cos(ang)*8, cy + sin(ang)*8,
                col=11, vx=cos(ang)*1.5, vy=sin(ang)*1.5))

    def _grant_xp(self, enemy):
        if isinstance(enemy, Ghost):     etype = "ghost"
        elif isinstance(enemy, Gargoyle): etype = "gargoyle"
        else:                             etype = "crab"
        gain = XP_PER_KILL.get(etype, 1)
        self.xp += gain
        self.levelup_flash = max(self.levelup_flash, 30)
        while (self.player_level < MAX_PLAYER_LEVEL and
               self.xp >= self.xp_for_next):
            self.player_level += 1
            self.levelup_flash = 90
            play(0, SND_LEVELUP)
            for i in range(20):
                ang = i / 20 * 6.283
                self.particles.append(Particle(
                    self.px+PW//2 + cos(ang)*6,
                    self.py+PH//2 + sin(ang)*6,
                    col=11, vx=cos(ang)*2.0, vy=sin(ang)*2.0))
            if self.player_level < MAX_PLAYER_LEVEL:
                self.xp_for_next += XP_THRESHOLDS[self.player_level]
            self.area_cd = 0

    def _current_area_radius(self):
        return AREA_RADIUS_PER_LEVEL[self.player_level]

    def _area_unlocked(self):
        return self.player_level >= 2

    def _update_area(self):
        if self.area_effect:
            self.area_effect["timer"] -= 1
            if self.area_effect["timer"] <= 0:
                self.area_effect = None

    def _update_portal(self):
        pd = self.portal_data
        if not pd or self.portal_timer > 0: return
        pa = (pd["ax"], pd["ay"], 10, 16)
        if rects_overlap((self.px, self.py, PW, PH), pa):
            self.px = float(pd["bx"])
            self.py = float(pd["by"])
            self.vy = 0
            self.portal_timer = 40
            play(0, SND_PORTAL)
            for i in range(12):
                self._spawn_p(self.px+4, self.py+6, 11)

    def _update_particles(self):
        for p in self.particles[:]:
            p.update()
            if p.dead: self.particles.remove(p)

    def _check_checkpoint(self):
        cp = self.level["checkpoint"]
        if not self.checkpoint_active:
            if rects_overlap((self.px,self.py,PW,PH), cp):
                self.checkpoint_active = True
                self.checkpoint_pos = (cp[0]+1, cp[1]+cp[3]-PH)
                play(0, SND_CP)

    def _update_switches(self):
        old = self.sw_active[:]
        self.sw_active = [False]*len(self.level["switches"])
        pr = (self.px, self.py, PW, PH)
        for i, sw in enumerate(self.level["switches"]):
            z = (sw[0], sw[1]-2, sw[2], sw[3]+3)
            if rects_overlap(pr, z):
                self.sw_active[i] = True; continue
            for c in self.clones:
                if c.get("alive") and "x" in c:
                    if rects_overlap((c["x"],c["y"],PW,PH), z):
                        self.sw_active[i] = True; break
        for i in range(len(self.sw_active)):
            if self.sw_active[i] and not old[i]:
                play(0, SND_SWITCH)
        self.door_open = all(self.sw_active) if self.sw_active else True

    def _check_hazards(self):
        if self.flash > 0: return
        # Spikes = sofort Game Over (alle Herzen verlieren)
        if self.spike_hit(self.px, self.py, PW, PH):
            self.hearts = 0
            self._die(instant=True); return
        pr = (self.px, self.py, PW, PH)
        hit = False
        for e in self.enemies:
            if rects_overlap(pr, e.hit_rect()):
                hit = True; break
        if not hit:
            for b in self.bullets:
                if b.owner == "enemy" and rects_overlap(pr, b.rect()):
                    hit = True; break
        if hit:
            self._take_damage(); return
        if self.py > HEIGHT + 40:
            self.hearts = 0
            self._die(instant=True)

    def _take_damage(self):
        """1 Herz verlieren. Bei 0 → Game Over."""
        if self.state != ST_PLAY: return
        self.hearts -= 1
        play(0, SND_IMPACT)
        if self.hearts <= 0:
            self.hearts = 0
            self._die()
        else:
            self.flash = 60   # Kurze Unverwundbarkeit + Blinken

    def _die(self, instant=False):
        if self.state == ST_PLAY:
            self.state = ST_DEAD
            self.dead_timer = 90
            play(0, SND_GAMEOVER)
            # Herzen bei neuem Spiel zurücksetzen
            self.hearts = MAX_HEARTS

    def _check_door(self):
        if self.door_open:
            if rects_overlap((self.px,self.py,PW,PH), self.level["door"]):
                # Level 4 (idx 4 = Boss) nur mit Max-Level
                next_idx = self.level_idx + 1
                if next_idx < len(LEVELS) and LEVELS[next_idx]["name"].startswith("LEVEL 4"):
                    if self.player_level < MAX_PLAYER_LEVEL:
                        self.tut_hint   = (f"Du brauchst Spieler-Level {MAX_PLAYER_LEVEL}!",
                                           f"Aktuell: Level {self.player_level} – besieg mehr Gegner!")
                        self.tut_hint_t = 140
                        return   # Tür blockiert
                self._next_level()

    def _next_level(self):
        play(0, SND_NEXT)
        idx = self.level_idx
        if idx < len(STORY_TEXTS):
            self.story_lines = STORY_TEXTS[idx]
            self.story_timer = 220
            self.state = ST_STORY
        else:
            self._advance_level()

    def _advance_level(self):
        nxt = self.level_idx + 1
        if nxt >= len(LEVELS):
            # Winning Sound + Ending-Animation
            play(0, SND_WIN)
            self.ending_timer = 0
            self.state = ST_ENDING
        else:
            self.load_level(nxt, reset_clones=True)
            self.state = ST_PLAY

    def _update_camera(self):
        target = self.px - WIDTH//2 + PW//2
        target = max(0, min(target, self.level["world_w"] - WIDTH))
        self.cam_x += (target - self.cam_x) * 0.12

    def _update_music(self):
        """Boss-Musik nur in Level 4, einmal starten."""
        if self.level_idx == 4 and self.anim_t == 1:
            play(1, SND_MUS_BOSS, loop=True)
        elif self.level_idx != 4 and self.anim_t == 1:
            stop(1)   # Musik stoppen wenn nicht Level 4

    def _spawn_p(self, x, y, col, n=1):
        for _ in range(n):
            self.particles.append(Particle(x, y, col))

    # ==========================================
    #  DRAW HAUPT-DISPATCHER
    # ==========================================
    def draw(self):
        if self.state == ST_TITLE:
            self._draw_title()
        elif self.state == ST_INTRO:
            self._draw_intro()
        elif self.state == ST_TUTORIAL:
            self._draw_tutorial_playback()
        elif self.state in (ST_PLAY, ST_DEAD):
            self._draw_world()
            self._draw_ui()
            if self.state == ST_DEAD:
                self._draw_dead_screen()
        elif self.state == ST_WIN:
            self._draw_win()
        elif self.state == ST_ENDING:
            self._draw_ending()
        elif self.state == ST_STORY:
            self._draw_story()

    # ---- Intro-Animation ----
    def _draw_intro(self):
        scene_idx = self.intro_scene
        if scene_idx >= len(INTRO_SCENES):
            cls(0); return
        scene = INTRO_SCENES[scene_idx]
        stype = scene["type"]

        if stype == "outside":
            self._draw_intro_outside()
        elif stype == "walk_in":
            self._draw_intro_walk_in()
        elif stype == "dungeon":
            self._draw_intro_dungeon()

        # Skip-Hinweis
        if (self.anim_t // 20) % 2 == 0:
            text(78, 134, "SPACE = UEBERSPRINGEN", 5)

    def _draw_intro_outside(self):
        """Blaue-Himmel-Wiese mit Held der nach rechts laeuft."""
        # Himmel
        cls(12)
        # Wolken
        t = self.anim_t
        for cx, cy, cw in [(30, 18, 40), (110, 12, 30), (180, 22, 50), (220, 10, 36)]:
            rx = (cx - t // 8) % (WIDTH + 60) - 30
            rect(rx, cy, cw, 12, 7)
            rect(rx+4, cy-6, cw-8, 8, 7)
            rectb(rx, cy, cw, 12, 6)
        # Sonne
        circ(220, 20, 12, 10)
        circb(220, 20, 13, 9)

        # Boden / Wiese
        rect(0, 110, WIDTH, 34, 3)       # Gras
        line(0, 110, WIDTH-1, 110, 11)   # Gras-Linie
        rect(0, 118, WIDTH, 26, 4)       # Erde

        # Graeschen
        for gx in range(4, WIDTH, 8):
            h2 = 3 + (gx * 13 % 4)
            line(gx, 110, gx+1, 110-h2, 3)
            line(gx+1, 110-h2, gx+2, 110, 11)

        # Baum links
        self._draw_tree(40, 75)
        self._draw_tree(195, 70)

        # Dungeon-Eingang rechts (Tor)
        self._draw_dungeon_entrance(220, 70)

        # Text-Box
        rect(12, 14, 230, 32, 0)
        rectb(12, 14, 230, 32, 10)
        text(22, 20, "Der Held hoert einen Schrei aus dem Dungeon.", 7)
        text(22, 30, "Prinzessin Lyra ist gefangen!", 10)
        text(22, 38, "Er macht sich auf den Weg...", 5)

        # Held laeuft nach rechts (echter Sprite, direktes blt)
        hx = int(self.intro_hero_x) % (WIDTH - 10)
        hy = 98
        fu = 16 if (self.anim_t // 8) % 2 == 0 else 0
        blt(hx + SPR_OX, hy + SPR_OY, 0, fu, 0, SPRITE_W, SPRITE_H, 0)

    def _draw_intro_walk_in(self):
        """Held betritt den Dungeon-Eingang."""
        cls(0)
        # Dungeon-Eingang Nahaufnahme
        rect(0, 80, WIDTH, 64, 4)    # Boden
        line(0, 80, WIDTH-1, 80, 13)

        # Mauerwerk
        for row in range(0, 80, 10):
            off = (row // 10) % 2 * 16
            for bx in range(-off, WIDTH, 32):
                rectb(bx, row, 32, 10, 2)

        # Grosses Tor in der Mitte
        rect(100, 30, 56, 50, 1)
        rectb(100, 30, 56, 50, 2)
        # Tor-Bogen
        circ(128, 30, 28, 1)
        circb(128, 30, 28, 2)
        # Tor offen (dunkel)
        rect(108, 40, 40, 40, 0)
        circ(128, 40, 20, 0)
        # Glimmen aus dem Dungeon
        col_glow = 9 if (self.anim_t // 8) % 2 == 0 else 8
        rect(110, 50, 36, 30, col_glow)
        circ(128, 50, 18, col_glow)
        rect(112, 52, 32, 28, 0)
        circ(128, 52, 16, 0)

        # Beschriftung
        text(96, 22, "DUNGEON LEVEL 1", 8)

        # Text-Box oben
        rect(12, 6, 230, 18, 0)
        rectb(12, 6, 230, 18, 9)
        text(22, 11, "Tiefer und tiefer geht es hinab...", 7)

        # Held laeuft ins Tor (echter Sprite, Screen-Koordinaten direkt)
        hx = min(int(self.intro_hero_x), 118)
        hy = 63
        is_moving = hx < 118
        # blt direkt: dx=hx+SPR_OX, dy=hy+SPR_OY
        fu = 16 if (is_moving and (self.anim_t // 8) % 2 == 0) else 0
        blt(hx + SPR_OX, hy + SPR_OY, 0, fu, 0, SPRITE_W, SPRITE_H, 0)

    def _draw_intro_dungeon(self):
        """Prinzessin und Drache im Dungeon-Raum."""
        # Dungeon-Raum
        cls(0)
        # Mauern
        for row in range(0, HEIGHT, 10):
            off = (row // 10) % 2 * 16
            for bx in range(-off, WIDTH, 32):
                rectb(bx, row, 32, 10, 2)

        # Boden
        rect(0, 118, WIDTH, 26, 13)
        line(0, 118, WIDTH-1, 118, 7)
        for bx in range(0, WIDTH, 16):
            rectb(bx, 118, 16, 10, 2)

        # Fackeln
        for tx in [30, 220]:
            rect(tx+1, 28, 4, 8, 4)
            rectb(tx+1, 28, 4, 8, 1)
            fc = 9 if (self.anim_t // 5) % 2 == 0 else 10
            rect(tx, 18, 6, 12, fc)
            rect(tx+1, 15, 4, 5, 7)
            pset(tx+2, 14, 7)

        # ---- Prinzessin (Lyra) ----
        self._draw_princess(80, 92)

        # ---- Drache ----
        self._draw_dragon(148, 78)

        # Feuer vom Drachen zur Prinzessin
        fire_t = (self.anim_t // 3) % 5
        for fi in range(8):
            fx = 158 + fi * 8 + fire_t
            fy = 104 + (fi % 3) - 2
            if fx < 94:
                col = [8, 9, 10, 9, 8][fire_t]
                rect(fx, fy, 6, 4, col)
                rect(fx+1, fy-1, 4, 2, 7)

        # Kettenbox fuer Prinzessin
        rectb(68, 108, 24, 10, 5)
        for cx2 in range(70, 90, 4):
            pset(cx2, 108, 5); pset(cx2, 117, 5)

        # Text
        rect(10, 8, 236, 26, 0)
        rectb(10, 8, 236, 26, 14)
        text(20, 14, "Prinzessin Lyra ist gefangen!", 14)
        text(20, 24, "Nur du kannst sie retten!", 7)

        # Held links im Bild (echter Sprite, direktes blt)
        blt(14 + SPR_OX, 106 + SPR_OY, 0, 0, 0, SPRITE_W, SPRITE_H, 0)

    def _draw_tree(self, x, y):
        """Kleiner Baum an Position x,y (Baumkrone Mitte)."""
        # Stamm
        rect(x-2, y+14, 5, 20, 4)
        # Krone
        circ(x, y, 14, 3)
        circ(x, y,  9, 11)
        # Schatten
        circb(x, y, 14, 1)
        # Aepfel
        for ax2, ay2 in [(x-5, y-3), (x+4, y+2), (x-2, y+6)]:
            pset(ax2, ay2, 8)

    def _draw_dungeon_entrance(self, x, y):
        """Kleines Dungeon-Tor."""
        rect(x, y, 28, 44, 13)
        rectb(x, y, 28, 44, 2)
        circ(x+14, y, 14, 13)
        circb(x+14, y, 14, 2)
        rect(x+4, y+8, 20, 36, 0)
        circ(x+14, y+8, 10, 0)
        # Schild
        rect(x+8, y+14, 12, 8, 4)
        rectb(x+8, y+14, 12, 8, 7)
        text(x+10, y+17, "DNG", 8)

    def _draw_simple_hero(self, x, y, facing=1, walking=False):
        """Einfacher gezeichneter Held fuer Intro-Szenen."""
        t = self.anim_t
        # Beine
        leg = t // 8 % 2 if walking else 0
        if facing == 1:
            line(x+4, y+10, x+2+leg, y+14, 7)
            line(x+4, y+10, x+6-leg, y+14, 7)
        else:
            line(x+4, y+10, x+2+leg, y+14, 7)
            line(x+4, y+10, x+6-leg, y+14, 7)
        # Koerper
        rect(x+1, y+4, 8, 7, 9)
        rectb(x+1, y+4, 8, 7, 1)
        # Arme
        arm = leg
        line(x+1, y+5, x-2+arm, y+9, 7)
        line(x+9, y+5, x+12-arm, y+9, 7)
        # Kopf
        rect(x+2, y, 7, 5, 7)
        rectb(x+2, y, 7, 5, 1)
        # Augen
        ex_off = 1 if facing == 1 else 0
        pset(x+4+ex_off, y+2, 0)
        # Haar (braun)
        line(x+2, y, x+8, y, 4)
        pset(x+2, y+1, 4)

    def _draw_princess(self, x, y):
        """
        Prinzessin Lyra: Blonde Haare, Pinkes Kleid.
        x, y = obere linke Ecke des Sprites (ca 16x28 Pixel).
        """
        t = self.anim_t
        # Haare (blond = Farbe 9/10)
        # Langer blonder Zopf
        rect(x+2, y+1, 9, 4, 9)      # Haare oben
        line(x+1, y+2, x, y+6, 9)    # linke Haarsträhne
        line(x+10, y+2, x+11, y+8, 9) # rechte Haarsträhne
        # Zopf hinten
        line(x+5, y+5, x+4, y+14, 9)
        line(x+4, y+14, x+3, y+18, 9)
        line(x+7, y+5, x+8, y+14, 9)

        # Krone
        pset(x+4, y, 10); pset(x+6, y-1, 10); pset(x+8, y, 10)
        line(x+3, y+1, x+10, y+1, 10)

        # Gesicht
        rect(x+3, y+2, 7, 5, 7)
        rectb(x+3, y+2, 7, 5, 1)
        # Augen (blau)
        pset(x+5, y+4, 12); pset(x+8, y+4, 12)
        # Mund
        pset(x+6, y+6, 8)

        # Pinkes Kleid
        rect(x+2, y+7, 10, 8, 14)    # Oberteil
        rectb(x+2, y+7, 10, 8, 8)
        # Kragendetail
        line(x+5, y+7, x+7, y+7, 7)
        # Rock – wird breiter nach unten
        tri(x, y+15, x+13, y+15, x+6, y+26, 14)
        line(x, y+15, x+13, y+15, 8)
        # Rockdetail (wellen)
        for ry in range(17, 26, 3):
            for rx2 in range(x+2, x+11, 3):
                pset(rx2, ry, 8)

        # Arme
        line(x+2, y+8, x-1, y+12, 7)
        line(x+10, y+8, x+13, y+12, 7)
        # Haende
        pset(x-1, y+12, 7); pset(x+13, y+12, 7)

        # Schuhe (pink/lila)
        rect(x+3, y+24, 3, 3, 14)
        rect(x+7, y+24, 3, 3, 14)
        rectb(x+3, y+24, 3, 3, 8)
        rectb(x+7, y+24, 3, 3, 8)

        # Gefangen-Kette
        if (t // 10) % 2 == 0:
            pset(x+5, y+26, 5)
            pset(x+7, y+26, 5)

    def _draw_dragon(self, x, y):
        """
        Kleiner Drache: Gruen, speit Feuer.
        x, y = obere linke Ecke, ca 40x32 px.
        """
        t = self.anim_t
        angry = (t // 12) % 2 == 0

        # Schwanz (geschwungen nach rechts)
        line(x+36, y+18, x+44, y+22, 3)
        line(x+44, y+22, x+48, y+18, 3)
        pset(x+48, y+17, 9)          # Schwanzspitze gelb

        # Hinterbeine
        line(x+26, y+24, x+24, y+30, 3)
        pset(x+22, y+30, 1); pset(x+24, y+31, 1); pset(x+26, y+30, 1)

        # Fluegel (ausgeklappt)
        wing_off = 1 if (t // 10) % 2 == 0 else 0
        # Linker Flügel
        rect(x+4,  y-8-wing_off, 12, 10, 1)
        rectb(x+4, y-8-wing_off, 12, 10, 3)
        line(x+5,  y-7-wing_off, x+14, y-2-wing_off, 3)
        line(x+10, y-7-wing_off, x+15, y-1-wing_off, 3)
        # Rechter Flügel
        rect(x+18, y-6-wing_off, 12, 8, 1)
        rectb(x+18, y-6-wing_off, 12, 8, 3)

        # Koerper
        rect(x+6, y+6,  26, 18, 3)
        rectb(x+6, y+6, 26, 18, 11)
        # Bauchschuppen
        line(x+8,  y+10, x+30, y+10, 11)
        line(x+8,  y+14, x+30, y+14, 11)
        line(x+8,  y+18, x+30, y+18, 11)

        # Vorderbeine / Klauen
        line(x+8,  y+24, x+6,  y+30, 3)
        pset(x+4,  y+30, 1); pset(x+6, y+31, 1); pset(x+8, y+30, 1)
        line(x+18, y+24, x+16, y+30, 3)
        pset(x+14, y+30, 1); pset(x+16, y+31, 1); pset(x+18, y+30, 1)

        # Hals
        rect(x+2, y, 8, 8, 3)
        rectb(x+2, y, 8, 8, 11)

        # Kopf (schaut links – speit Feuer)
        rect(x-8, y-6, 14, 10, 3)
        rectb(x-8, y-6, 14, 10, 11)
        # Hörner
        line(x-6, y-6, x-8, y-10, 11)
        line(x-2, y-6, x-4, y-10, 11)
        pset(x-8, y-10, 9); pset(x-4, y-10, 9)
        # Auge (wuetend)
        ec = 8 if angry else 9
        rect(x-6, y-4, 3, 3, ec)
        pset(x-5, y-3, 0)
        line(x-7, y-6, x-5, y-5, 11)   # Augenbraue
        # Maul
        rect(x-14, y, 8, 4, 3)
        rectb(x-14, y, 8, 4, 11)
        # Zaehne
        pset(x-12, y, 7); pset(x-10, y, 7); pset(x-8, y, 7)
        # Zunge
        rect(x-14, y+2, 4, 2, 8)

        # Feuer-Atem (animiert)
        fire_frames = (t // 4) % 4
        fire_cols = [8, 9, 10, 9]
        for fi in range(5):
            fx = x - 18 - fi * 10
            fy = y + 1 + (fi % 2) - 1
            fc = fire_cols[(fi + fire_frames) % 4]
            if fi < 3:
                rect(fx, fy, 9, 5, fc)
                pset(fx+3, fy-1, 7)
            else:
                rect(fx, fy, 6, 4, fc)

    # ---- Tutorial-Playback Zeichnen ----
    def _draw_tutorial_playback(self):
        # Hintergrund + Welt manuell (statt _draw_world) damit kein flash-Blinken)
        self._draw_bg()
        lv = self.level
        for p in lv["platforms"]: self._draw_platform(*p)
        for s in lv["spikes"]:    self._draw_spikes(*s)
        for i, sw in enumerate(lv["switches"]): self._draw_switch(*sw, self.sw_active[i])
        self._draw_door()
        self._draw_checkpoint()
        self._draw_portal()
        for c in self.clones:
            if c.get("alive") and "x" in c:
                self._draw_actor(c["x"]-self.cam_x, c["y"], c["facing"], ghost=True)
        for e in self.enemies:
            esx = e.x - self.cam_x
            if -20 < esx < WIDTH+20:
                e.draw(self.cam_x, self.anim_t)
        for b in self.bullets: b.draw(self.cam_x)
        for p in self.particles: p.draw(self.cam_x)
        self._draw_area_effect()
        # Spieler IMMER zeichnen ohne Blink-Effekt, mit korrekter Walk-Animation
        step = TUT_SCRIPT[self.tut_step] if self.tut_step < len(TUT_SCRIPT) else None
        is_moving = (step is not None and
                     self.tut_phase == "action" and
                     step["type"] in ("move_right", "jump_and_move", "shoot", "move_to_switch") and
                     self.on_ground)
        self._draw_actor(self.px - self.cam_x, self.py, self.facing, walking=is_moving)
        self._draw_ui()
        # Tutorial-Hint-Box
        if self.tut_hint_t > 0 and self.tut_hint[0]:
            bx2 = WIDTH//2 - 72
            rect(bx2, 44, 144, 26, 0)
            rectb(bx2, 44, 144, 26, 10)
            text(bx2+4, 48, self.tut_hint[0], 10)
            if self.tut_hint[1]:
                text(bx2+4, 58, self.tut_hint[1], 7)
        # Phase-Anzeige
        phase_txt = "SCHAU ZU..." if self.tut_phase == "action" else "LIES..."
        text(WIDTH - 70, 138, phase_txt, 5)

    # ---- Welt zeichnen ----
    def _draw_bg(self):
        lv = self.level
        cls(lv["bg"])
        bg2 = lv["bg2"]; wl = lv["wall_line"]
        rect(0, 10, WIDTH, HEIGHT-10, bg2)
        for y in range(10, HEIGHT, 12):
            line(0, y, WIDTH-1, y, wl)
        row = 0
        for y in range(10, HEIGHT, 12):
            off = int(-self.cam_x * 0.15) % 32
            if row % 2 == 1: off = (off+16)%32
            for x in range(off-32, WIDTH+32, 32):
                line(x, y, x, y+11, wl)
                if 0 < x+8 < WIDTH: pset(x+8, y+4, 7)
            row += 1
        pc = lv["plant_col"]; ph2 = lv["plant_hi"]; pot = lv["pot_col"]
        for bx2, by2 in lv.get("plants",[]):
            sx = bx2 - int(self.cam_x)
            if -12 < sx < WIDTH+12:
                rect(sx, by2, 8, 5, pot); rectb(sx, by2, 8, 5, wl)
                line(sx+1, by2, sx+6, by2, pc)
                for oy2 in range(5, 10): pset(sx+2+oy2%3, by2+oy2, pc)
        tf=lv["tf"]; tg=lv["tg"]; tb2=lv["tb"]
        for tx, ty in lv["torches"]:
            sx = tx - int(self.cam_x)
            if -8 < sx < WIDTH+8:
                rect(sx+1, ty+6, 4, 5, tb2); rectb(sx+1, ty+6, 4, 5, wl)
                f2 = (self.anim_t // 4) % 4
                rect(sx+(f2%2), ty+(f2>1), 6-f2%2, 6, tf)
                rect(sx+1, ty-1, 4, 3, tg)
                pset(sx+2, ty-2, 7)

    def _draw_platform(self, x, y, w, h):
        lv = self.level; sx = int(x - self.cam_x)
        rect(sx, y, w, h, lv["plat"])
        line(sx+1, y, sx+w-2, y, lv["plat_hi"])
        line(sx, y+h-1, sx+w-1, y+h-1, lv["plat_e"])
        line(sx, y, sx, y+h-1, lv["plat_e"])
        line(sx+w-1, y, sx+w-1, y+h-1, lv["plat_e"])
        if w > 14:
            for fx in range(sx+10, sx+w-2, 10):
                pset(fx, y+1, lv["plat_e"]); pset(fx, y+2, lv["plat_e"])

    def _draw_spikes(self, x, y, w, h):
        sx = int(x - self.cam_x); sc = self.level["spike"]
        for px2 in range(sx, sx+w, 4):
            tri(px2, y+h, px2+2, y, px2+4, y+h, sc)
            line(px2, y+h, px2+2, y, 7); line(px2+2, y, px2+4, y+h, 7)

    def _draw_switch(self, x, y, w, h, active):
        sx = int(x - self.cam_x)
        rect(sx, y, w, h, 11 if active else 2)
        rectb(sx, y, w, h, 1)
        rect(sx+2, y-2, w-4, 2, 10 if active else 5)

    def _draw_door(self):
        x,y,w,h = self.level["door"]; sx = int(x - self.cam_x)
        rect(sx, y, w, h, 11 if self.door_open else 2)
        rectb(sx, y, w, h, 7)
        rect(sx+3, y+4, 6, h-8, 10 if self.door_open else 1)
        if self.door_open and self.anim_t%8 < 4: rectb(sx-1,y-1,w+2,h+2,11)
        pset(sx+w-3, y+h//2, 7)

    def _draw_checkpoint(self):
        x,y,w,h = self.level["checkpoint"]; sx = int(x - self.cam_x)
        pole = 6 if self.checkpoint_active else 5
        flag = 10 if self.checkpoint_active else 8
        rect(sx+4, y, 2, h, pole)
        rect(sx+6, y+2, 6, 4, flag); rectb(sx+6, y+2, 6, 4, 7)
        rect(sx+1, y+h, 8, 2, self.level["plat"])

    def _draw_portal(self):
        pd = self.portal_data
        if not pd: return
        for bx2, by2, col in [
            (pd["ax"], pd["ay"], 11),
            (pd["bx"], pd["by"], 13),
        ]:
            sx = int(bx2 - self.cam_x)
            sy = int(by2)
            pulse = (self.anim_t // 4) % 4
            circb(sx+5, sy+8, 8+pulse, col)
            circb(sx+5, sy+8, 5,       col)
            rect(sx+1, sy, 8, 16, 0)
            f3 = (self.anim_t // 6) % 3
            for oy3 in range(2, 14, 2):
                pset(sx+4+(f3%2), sy+oy3, col)
                pset(sx+6-(f3%2), sy+oy3, col)
            text(sx-2, sy-7, "A" if col==11 else "B", col)

    def _draw_area_effect(self):
        ae = self.area_effect
        if not ae: return
        cx = int(ae["cx"] - self.cam_x); cy = int(ae["cy"])
        frac = ae["timer"] / ae["max"]
        r = int(ae["radius"] * (1 - frac * 0.3))
        col = 11 if frac > 0.5 else 10
        circb(cx, cy, r,   col)
        circb(cx, cy, r-4, col)
        if frac > 0.7:
            circb(cx, cy, r-8, 7)

    def _draw_actor(self, x, y, facing_dir, ghost=False, walking=None):
        dx = int(x + SPR_OX); dy = int(y + SPR_OY)
        if walking is None:
            mv = (abs(self.vx) > 0.05 and self.on_ground) if not ghost else False
        else:
            mv = walking
        fu = 16 if mv else 0
        if ghost:
            pal(8,13); pal(9,12); pal(10,6); pal(4,5); pal(15,7); pal(14,6)
        if facing_dir == 1:
            blt(dx, dy, 0, fu, 0,  SPRITE_W, SPRITE_H, 0)
        else:
            blt(dx+SPRITE_W, dy, 0, fu, 0, -SPRITE_W, SPRITE_H, 0)
        if ghost: pal()

    def _draw_world(self):
        self._draw_bg()
        lv = self.level
        for p in lv["platforms"]: self._draw_platform(*p)
        for s in lv["spikes"]:    self._draw_spikes(*s)
        for i,sw in enumerate(lv["switches"]): self._draw_switch(*sw, self.sw_active[i])
        self._draw_door()
        self._draw_checkpoint()
        self._draw_portal()
        for c in self.clones:
            if c.get("alive") and "x" in c:
                self._draw_actor(c["x"]-self.cam_x, c["y"], c["facing"], ghost=True)
        for e in self.enemies:
            sx = e.x - self.cam_x
            if -20 < sx < WIDTH+20:
                e.draw(self.cam_x, self.anim_t)
        for b in self.bullets: b.draw(self.cam_x)
        for p in self.particles: p.draw(self.cam_x)
        self._draw_area_effect()
        if self.flash % 4 != 1:
            is_walking = (abs(self.vx) > 0.05 and self.on_ground)
            self._draw_actor(self.px-self.cam_x, self.py, self.facing,
                             walking=is_walking)

    def _draw_ui(self):
        lv = self.level
        pe = lv["plat_e"]
        rect(0, 0, WIDTH, 9, 0)
        line(0, 9, WIDTH-1, 9, pe)
        text(4, 2, lv["name"], 7)
        text(80, 2, f"ECHOS:{len(self.clones)}", 7)
        text(145, 2, f"LV{self.level_idx+1}/{len(LEVELS)}", 7)
        # Herzen (3 Pixel-Herzen)
        for hi in range(MAX_HEARTS):
            hcol = 8 if hi < self.hearts else 1   # rot=voll, dunkel=leer
            hx2 = 4 + hi * 10
            hy2 = 138
            # Pixel-Herz (5x5)
            pset(hx2+1, hy2,   hcol); pset(hx2+3, hy2,   hcol)
            pset(hx2,   hy2+1, hcol); pset(hx2+1, hy2+1, hcol)
            pset(hx2+2, hy2+1, hcol); pset(hx2+3, hy2+1, hcol)
            pset(hx2+4, hy2+1, hcol)
            pset(hx2,   hy2+2, hcol); pset(hx2+1, hy2+2, hcol)
            pset(hx2+2, hy2+2, hcol); pset(hx2+3, hy2+2, hcol)
            pset(hx2+4, hy2+2, hcol)
            pset(hx2+1, hy2+3, hcol); pset(hx2+2, hy2+3, hcol)
            pset(hx2+3, hy2+3, hcol)
            pset(hx2+2, hy2+4, hcol)
        plv = self.player_level
        lvtxt = f"PLV{plv}" + ("*" if plv == MAX_PLAYER_LEVEL else "")
        text(188, 2, lvtxt, 11 if self.levelup_flash > 0 else 6)
        bar_x = 212
        xp_w  = 50
        rect(bar_x, 3, xp_w, 4, 1)
        if plv < MAX_PLAYER_LEVEL:
            prev = sum(XP_THRESHOLDS[1:plv]) if plv > 1 else 0
            needed = XP_THRESHOLDS[plv]
            earned = self.xp - prev
            fill = int(min(earned / needed, 1.0) * xp_w)
            rect(bar_x, 3, fill, 4, 10)
        else:
            rect(bar_x, 3, xp_w, 4, 11)
        if self._area_unlocked():
            cd_col = 11 if self.area_cd == 0 else 2
            bw2 = int((1 - self.area_cd/AREA_CD) * xp_w) if self.area_cd > 0 else xp_w
            rect(bar_x, 8, xp_w, 2, 1)
            rect(bar_x, 8, bw2,  2, cd_col)
        else:
            text(bar_x, 2, "C:LVL2", 5)
        if self.levelup_flash > 60:
            rect(44, 52, 168, 22, 0)
            rectb(44, 52, 168, 22, 11)
            col = 11 if (self.anim_t % 6 < 3) else 10
            text(76, 58, f"SPIELER LEVEL {self.player_level}!", col)
            if self.player_level == 2:
                text(60, 66, "FLAECHENANGRIFF FREIGESCHALTET", 7)
            elif self.player_level > 2:
                text(52, 66, f"FLAECHENANGRIFF RADIUS +{10*(self.player_level-1)}", 7)
        rect(0, 136, WIDTH, 8, 0)
        line(0, 136, WIDTH-1, 136, pe)
        # Herzen werden oben schon gezeichnet, Controls rechts
        text(38, 138, "X=SHOOT  C=AREA  SPC=JUMP  R=RST", 5)

    def _draw_dead_screen(self):
        """Game Over Bildschirm."""
        rect(44, 54, 168, 36, 0)
        rectb(44, 54, 168, 36, 8)
        col = 8 if (self.anim_t//6)%2==0 else 7
        text(88, 62, "GAME  OVER", col)
        text(64, 72, "ALLE HERZEN VERLOREN", 5)
        if self.dead_timer > 30:
            text(68, 82, "Weiter in Kuerze...", 1)

    def _draw_title(self):
        lv = LEVELS[1]
        cls(lv["bg"])
        rect(0, 12, WIDTH, HEIGHT-12, lv["bg2"])
        wl = lv["wall_line"]
        for y in range(12, HEIGHT, 12): line(0, y, WIDTH-1, y, wl)
        row=0
        for y in range(12, HEIGHT, 12):
            off = 0 if row%2==0 else 16
            for x in range(off, WIDTH, 32):
                line(x, y, x, y+11, wl)
                if 0 < x+8 < WIDTH: pset(x+8, y+4, 7)
            row+=1
        rect(40, 20, 176, 36, 0); rectb(40, 20, 176, 36, lv["plat_hi"])
        text(90, 28, "TIME  ECHO", 7)
        text(86, 38, "LABYRINTH", 10)
        text(114, 48, "v 4.0", 5)
        rect(108, 64, 40, 30, 0); rectb(108, 64, 40, 30, lv["plat_hi"])
        blt(120, 70, 0, 0, 0, 16, 16, 0)
        tf=lv["tf"]; tg=lv["tg"]; tb2=lv["tb"]
        for tx,ty in [(48,62),(200,62)]:
            rect(tx+1,ty+6,4,5,tb2); rectb(tx+1,ty+6,4,5,wl)
            f2=(self.anim_t//4)%4
            rect(tx+(f2%2),ty+(f2>1),6,6,tf); rect(tx+1,ty-1,4,3,tg); pset(tx+2,ty-2,7)
        # Option-Boxen
        rect(24, 96, 96, 28, 0); rectb(24, 96, 96, 28, 10)
        text(36, 102, "T = TUTORIAL", 10)
        text(36, 112, "(inkl. Story-Intro)", 5)
        rect(136, 96, 96, 28, 0); rectb(136, 96, 96, 28, 11)
        text(148, 102, "SPACE = START", 11)
        text(148, 112, "(direkt Level 1)", 5)
        rect(0, 130, WIDTH, HEIGHT-130, lv["plat"])
        line(0, 130, WIDTH-1, 130, lv["plat_hi"])
        for x in range(0, WIDTH, 4):
            tri(x, 130, x+2, 124, x+4, 130, lv["spike"])

    def _draw_story(self):
        cls(0)
        for i in range(24):
            sx2 = (i*37 + self.anim_t//4) % WIDTH
            sy2 = (i*23) % (HEIGHT-20)
            pset(sx2, sy2, 5 if i%3==0 else 1)
        rect(24, 42, WIDTH-48, 60, 1)
        rectb(24, 42, WIDTH-48, 60, 10)
        for i, ln in enumerate(self.story_lines):
            text(34, 50+i*9, ln, 7 if i > 0 else 10)
        if self.story_timer < 180 or (self.anim_t//16)%2 == 0:
            text(88, 110, "SPACE ZUM WEITER", 5)

    def _draw_ending(self):
        """Ending-Animation: Held + Prinzessin draussen mit Konfetti und Musik."""
        t = self.ending_timer
        # Musik starten
        if t == 1:
            play(1, SND_MUS_END, loop=True)

        # Hintergrund: blauer Himmel wie Intro-Aussen
        cls(12)
        # Wolken
        for cx2, cy2, cw in [(30,18,40),(110,12,30),(180,22,50),(220,10,36)]:
            rx = (cx2 - t//6) % (WIDTH+60) - 30
            rect(rx, cy2, cw, 12, 7)
            rect(rx+4, cy2-6, cw-8, 8, 7)
            rectb(rx, cy2, cw, 12, 6)
        circ(220, 20, 12, 10); circb(220, 20, 13, 9)
        # Boden
        rect(0, 110, WIDTH, 34, 3)
        line(0, 110, WIDTH-1, 110, 11)
        rect(0, 118, WIDTH, 26, 4)
        for gx2 in range(4, WIDTH, 8):
            h2 = 3 + (gx2*13%4)
            line(gx2, 110, gx2+1, 110-h2, 3)
            line(gx2+1, 110-h2, gx2+2, 110, 11)
        # Bäume
        self._draw_tree(30, 76)
        self._draw_tree(200, 72)

        # Konfetti (viele bunte Pixel)
        for i in range(40):
            kx = (i*47 + t*2 + i*13) % WIDTH
            ky = (i*31 + t + i*7) % (HEIGHT - 30)
            col2 = [8,9,10,11,14,7][i%6]
            rect(kx, ky, 2, 2, col2)
            # Fallende Konfetti
            ky2 = (i*23 + t*3) % (HEIGHT - 20)
            pset((i*37+t)%WIDTH, ky2, col2)

        # Held und Prinzessin nebeneinander (erscheinen nach kurzer Zeit)
        if t > 40:
            hero_x = 100
            hero_y = 98
            # Held
            blt(hero_x + SPR_OX, hero_y + SPR_OY, 0, 0, 0, SPRITE_W, SPRITE_H, 0)
            # Prinzessin daneben
            self._draw_princess(hero_x + 20, hero_y - 5)
            # Herzchen zwischen ihnen (pulsierend)
            if (t//8)%2==0:
                heart_x = hero_x + 14
                heart_y = hero_y - 10
                pset(heart_x+1, heart_y,   8); pset(heart_x+3, heart_y,   8)
                pset(heart_x,   heart_y+1, 8); pset(heart_x+2, heart_y+1, 8)
                pset(heart_x+4, heart_y+1, 8)
                pset(heart_x+1, heart_y+2, 8); pset(heart_x+3, heart_y+2, 8)
                pset(heart_x+2, heart_y+3, 8)

        # Text-Box
        if t > 80:
            rect(20, 10, WIDTH-40, 28, 0)
            rectb(20, 10, WIDTH-40, 28, 14)
            col3 = 14 if (t//12)%2==0 else 10
            text(50, 16, "PRINZESSIN LYRA IST FREI!", col3)
            text(44, 25, "Das Labyrinth ist besiegt!", 7)

        if t > 200:
            text(72, 132, "SPACE = WEITER", 5 if (t//16)%2==0 else 1)

    def _draw_win(self):
        cls(0)
        for y in range(0, HEIGHT, 8):
            col = 1+(y//8+self.anim_t//8)%3
            line(0, y, WIDTH-1, y, col)
        rect(24, 16, WIDTH-48, 112, 0)
        rectb(24, 16, WIDTH-48, 112, 11)
        text(68, 24, "ABENTEUER BEENDET!", 11)
        text(44, 36, "DER DRACHE IST BESIEGT!", 8)
        text(44, 46, "PRINZESSIN LYRA IST FREI!", 14)
        text(44, 58, "DIE ZEITSCHLEIFE IST GEBROCHEN.", 10)
        text(50, 70, f"Spieler-Level: {self.player_level}", 11)
        text(50, 80, f"Gesammelte XP: {self.xp}", 7)
        text(72, 96, "R = TITEL", 10)


# ===========================================
#  MAIN
# ===========================================

init(WIDTH, HEIGHT, title="Time Echo Labyrinth", fps=60)
load("res.pyxres")

game = Game()
run(game.update, game.draw)