import math
import random
import pyxel
import json
import os
SCREEN_W = 320
SCREEN_H = 200
FPS = 60
GROUND_Y = 168
PLAYER_W = 16
PLAYER_H = 24
GRAVITY = 0.28
MAX_FALL = 7.2
LEVEL_DISTANCE = 2400
CAMPAIGN_LEVELS = 10
SAVE_FILE = "save.json"
C0 = 0
C1 = 1
C2 = 2
C3 = 3
C4 = 4
C5 = 5
C6 = 6
C7 = 7
C8 = 8
C9 = 9
C10 = 10
C11 = 11
C12 = 12
C13 = 13
C14 = 14
C15 = 15
def clamp(v, lo, hi):
    return max(lo, min(hi, v))
def rects_overlap(ax, ay, aw, ah, bx, by, bw, bh):
    return ax < bx + bw and ax + aw > bx and ay < by + bh and ay + ah > by
def dist2(ax, ay, bx, by):
    dx = ax - bx
    dy = ay - by
    return dx * dx + dy * dy
def sign(v):
    if v < 0:
        return -1
    if v > 0:
        return 1
    return 0
COUNTRIES = [
    {"key": "usa", "name": "USA"},
    {"key": "mexico", "name": "MEXICO"},
    {"key": "china", "name": "CHINA"},
    {"key": "germany", "name": "GERMANY"},
    {"key": "serbia", "name": "SERBIA"},
    {"key": "iran", "name": "IRAN"},
    {"key": "israel", "name": "ISRAEL"},
    {"key": "antarctica", "name": "ANTARCTICA"},
    {"key": "italy", "name": "ITALY"},
    {"key": "russia", "name": "RUSSIA"},
    {"key": "poland", "name": "POLAND"},
    {"key": "france", "name": "FRANCE"},
    {"key": "nigeria", "name": "NIGERIA"},
    {"key": "switzerland", "name": "SWITZERLAND"},
    {"key": "canada", "name": "CANADA"},
    {"key": "ukraine", "name": "UKRAINE"},
    {"key": "turkiye", "name": "TURKEY"},
    {"key": "south_korea", "name": "SOUTH KOREA"},
    {"key": "north_korea", "name": "NORTH KOREA"},
    {"key": "saudi", "name": "SAUDI ARABIA"},
    {"key": "belgium", "name": "BELGIUM"},
    {"key": "austria", "name": "AUSTRIA"},
    {"key": "romania", "name": "ROMANIA"},
    {"key": "japan", "name": "JAPAN"},
    {"key": "uk", "name": "UK"},
    {"key": "spain", "name": "SPAIN"},
    {"key": "brazil", "name": "BRAZIL"},
    {"key": "australia", "name": "AUSTRALIA"},
    {"key": "south_africa", "name": "SOUTH AFRICA"},
]
SKIN_PRICES = [0, 40, 70, 115, 180, 270, 390, 550]
COUNTRY_OUTLINES = {
    "usa": [(0,8),(3,6),(7,5),(11,5),(15,4),(20,5),(24,4),(29,5),(34,6),(38,8),(35,11),(28,11),(23,12),(18,11),(12,12),(7,11),(3,10)],
    "mexico": [(4,5),(8,4),(12,5),(16,7),(19,10),(20,14),(17,18),(14,17),(12,21),(10,20),(9,16),(6,14),(4,10)],
    "china": [(3,6),(8,3),(13,4),(18,3),(24,4),(29,6),(33,9),(31,13),(25,14),(21,13),(16,15),(10,14),(5,11)],
    "germany": [(8,3),(12,2),(16,4),(18,8),(17,13),(14,17),(10,18),(7,14),(6,9)],
    "serbia": [(8,4),(12,4),(15,7),(14,11),(12,15),(8,16),(5,12),(5,8)],
    "iran": [(4,7),(9,4),(15,4),(21,4),(27,6),(31,9),(29,13),(23,15),(15,15),(9,14),(5,11)],
    "israel": [(9,3),(13,3),(15,6),(15,12),(13,16),(10,16),(8,12),(8,6)],
    "antarctica": [(4,18),(10,13),(16,11),(23,10),(29,11),(36,13),(40,17),(35,20),(25,21),(14,21),(7,20)],
    "india": [(13,3),(17,4),(20,7),(22,11),(20,15),(17,18),(14,22),(11,18),(9,13),(8,8)],
    "italy": [(12,3),(16,3),(17,7),(15,11),(18,15),(16,19),(13,22),(11,18),(12,13),(9,9)],
    "russia": [(2,7),(9,4),(17,4),(24,4),(31,5),(39,6),(44,8),(42,12),(36,13),(29,12),(23,14),(16,14),(9,12),(4,11)],
    "poland": [(7,4),(13,4),(17,7),(17,11),(13,15),(8,15),(5,10)],
    "france": [(7,4),(12,3),(16,5),(17,9),(15,13),(11,16),(7,14),(4,10),(5,6)],
    "nigeria": [(7,4),(13,4),(15,8),(15,13),(11,16),(7,15),(5,10)],
    "switzerland": [(8,4),(13,4),(16,8),(15,13),(10,15),(6,12),(5,8)],
    "canada": [(5,6),(8,3),(12,5),(16,3),(20,5),(24,3),(29,5),(33,9),(31,14),(27,18),(20,19),(13,18),(8,15)],
    "ukraine": [(5,6),(11,5),(17,5),(23,6),(27,9),(24,13),(18,14),(10,14),(5,11)],
    "turkiye": [(5,6),(11,5),(16,5),(22,6),(26,9),(25,12),(20,14),(12,14),(7,12),(4,9)],
    "south_korea": [(8,4),(13,4),(17,7),(17,11),(14,15),(9,15),(5,11),(5,7)],
    "north_korea": [(6,5),(12,5),(17,6),(18,10),(16,14),(11,15),(6,13),(4,9)],
    "saudi": [(5,7),(12,5),(19,5),(27,6),(31,9),(31,13),(25,16),(17,16),(10,15),(6,12)],
    "belgium": [(7,4),(12,4),(15,6),(15,11),(12,15),(8,15),(5,11),(5,7)],
    "austria": [(5,7),(11,5),(18,5),(25,6),(29,8),(27,12),(21,14),(13,15),(6,12)],
    "romania": [(6,5),(11,4),(16,5),(18,9),(17,13),(12,16),(7,15),(5,11)],
    "japan": [(12,3),(15,5),(16,9),(15,13),(12,16),(9,13),(8,8),(9,5)],
    "uk": [(7,4),(11,3),(15,5),(16,10),(14,14),(10,17),(6,15),(4,10),(5,6)],
    "spain": [(4,7),(10,5),(17,5),(23,6),(27,9),(24,13),(17,15),(9,14),(4,11)],
    "brazil": [(7,4),(13,4),(19,6),(22,10),(21,15),(17,19),(10,18),(6,13),(5,8)],
    "australia": [(6,8),(12,5),(19,5),(25,7),(29,11),(27,16),(21,18),(14,18),(8,15),(5,11)],
    "south_africa": [(5,7),(10,5),(16,5),(21,7),(24,11),(21,15),(14,16),(8,14),(4,10)],
}
def draw_country_outline(key, x, y, scale=3, color=C7, fill=False):
    pts = COUNTRY_OUTLINES.get(key)
    if not pts:
        return
    scaled = [(x + px * scale, y + py * scale) for px, py in pts]
    if fill:
        cx = sum(px for px, _ in scaled) // len(scaled)
        cy = sum(py for _, py in scaled) // len(scaled)
        for i in range(len(scaled)):
            x1, y1 = scaled[i]
            x2, y2 = scaled[(i + 1) % len(scaled)]
            pyxel.tri(cx, cy, x1, y1, x2, y2, color)
    for i in range(len(scaled)):
        x1, y1 = scaled[i]
        x2, y2 = scaled[(i + 1) % len(scaled)]
        pyxel.line(x1, y1, x2, y2, color)
def load_save():
    """Load player progress, repairing bad or old save files safely."""
    data = {
        "best_survival": 0,
        "coins_total": 0,
        "unlocked_skins": [0],
        "selected_country": 0,
        "extra_hp_bonus": 0,
    }
    try:
        if os.path.exists(SAVE_FILE):
            with open(SAVE_FILE, "r", encoding="utf-8") as f:
                raw = json.load(f)
            data["best_survival"] = max(0, int(raw.get("best_survival", 0)))
            data["coins_total"] = max(0, int(raw.get("coins_total", 0)))
            data["selected_country"] = clamp(int(raw.get("selected_country", 0)), 0, len(COUNTRIES) - 1)
            data["extra_hp_bonus"] = clamp(int(raw.get("extra_hp_bonus", 0)), 0, 20)
            unlocked = raw.get("unlocked_skins", [0])
            if not isinstance(unlocked, list):
                unlocked = [0]
            cleaned = []
            for skin_id in unlocked:
                try:
                    skin_id = int(skin_id)
                except (TypeError, ValueError):
                    continue
                if 0 <= skin_id < len(SKIN_PRICES) and skin_id not in cleaned:
                    cleaned.append(skin_id)
            if 0 not in cleaned:
                cleaned.insert(0, 0)
            data["unlocked_skins"] = cleaned
    except (OSError, json.JSONDecodeError, TypeError, ValueError):
        pass
    return data
def save_save(data):
    """Write progress atomically so a crash cannot corrupt save.json."""
    tmp_file = SAVE_FILE + ".tmp"
    try:
        with open(tmp_file, "w", encoding="utf-8") as f:
            json.dump(data, f, separators=(",", ":"))
        os.replace(tmp_file, SAVE_FILE)
    except OSError:
        try:
            if os.path.exists(tmp_file):
                os.remove(tmp_file)
        except OSError:
            pass
def setup_audio():
    try:
        pyxel.sound(0).set("c3g3c4", "p", "764", "n", 18)
        pyxel.sound(1).set("c2a1f1", "n", "7642", "f", 20)
        pyxel.sound(2).set("f2c2", "n", "77", "f", 24)
        pyxel.sound(3).set("c3e3g3", "s", "753", "n", 14)
        pyxel.sound(4).set("c4d4", "p", "75", "n", 20)
        pyxel.sound(5).set("g2a2", "p", "76", "n", 14)
        pyxel.sound(6).set("c4", "s", "7", "n", 8)
        pyxel.sound(7).set("c2g1", "n", "77", "n", 28)
        pyxel.sound(8).set("c3e3g3c4 g3e3c3g2", "s", "6543", "vvvv", 10)
    except Exception:
        pass
def sfx(ch, snd):
    try:
        pyxel.play(ch, snd)
    except Exception:
        pass
def play_story_music():
    try:
        pyxel.play(3, 8, loop=True)
    except Exception:
        pass
def stop_story_music():
    try:
        pyxel.stop(3)
    except Exception:
        pass
def draw_flag(key, x, y, w=16, h=10, wave=0):
    ox = int(math.sin(wave) * 1.0)
    def rx(v):
        return x + v + ox
    pyxel.rectb(rx(0), y, w, h, C7)
    if key == "usa":
        for i in range(h):
            pyxel.line(rx(0), y + i, rx(w - 1), y + i, C8 if i % 2 == 0 else C7)
        pyxel.rect(rx(0), y, w // 2, h // 2 + 1, C1)
    elif key == "mexico":
        pyxel.rect(rx(0), y, w // 3, h, C3)
        pyxel.rect(rx(w // 3), y, w // 3, h, C7)
        pyxel.rect(rx(2 * w // 3), y, w - 2 * (w // 3), h, C8)
        pyxel.pset(rx(w // 2), y + h // 2, C4)
    elif key == "china":
        pyxel.rect(rx(0), y, w, h, C8)
        pyxel.pset(rx(3), y + 2, C10)
        pyxel.pset(rx(2), y + 3, C10)
        pyxel.pset(rx(3), y + 3, C10)
        pyxel.pset(rx(4), y + 3, C10)
        pyxel.pset(rx(3), y + 4, C10)
    elif key == "germany":
        pyxel.rect(rx(0), y, w, h // 3, C0)
        pyxel.rect(rx(0), y + h // 3, w, h // 3, C8)
        pyxel.rect(rx(0), y + 2 * (h // 3), w, h - 2 * (h // 3), C10)
    elif key == "serbia":
        pyxel.rect(rx(0), y, w, h // 3, C8)
        pyxel.rect(rx(0), y + h // 3, w, h // 3, C1)
        pyxel.rect(rx(0), y + 2 * (h // 3), w, h - 2 * (h // 3), C7)
        pyxel.pset(rx(4), y + h // 2, C10)
    elif key == "iran":
        pyxel.rect(rx(0), y, w, h // 3, C3)
        pyxel.rect(rx(0), y + h // 3, w, h // 3, C7)
        pyxel.rect(rx(0), y + 2 * (h // 3), w, h - 2 * (h // 3), C8)
    elif key == "israel":
        pyxel.rect(rx(0), y, w, h, C7)
        pyxel.rect(rx(0), y + 1, w, 1, C12)
        pyxel.rect(rx(0), y + h - 2, w, 1, C12)
        pyxel.pset(rx(w // 2), y + h // 2, C12)
    elif key == "antarctica":
        pyxel.rect(rx(0), y, w, h, C12)
        pyxel.tri(rx(w // 2), y + 1, rx(4), y + h - 2, rx(w - 4), y + h - 2, C7)
    elif key == "india":
        pyxel.rect(rx(0), y, w, h // 3, C10)
        pyxel.rect(rx(0), y + h // 3, w, h // 3, C7)
        pyxel.rect(rx(0), y + 2 * (h // 3), w, h - 2 * (h // 3), C3)
        pyxel.pset(rx(w // 2), y + h // 2, C12)
    elif key == "italy":
        pyxel.rect(rx(0), y, w // 3, h, C3)
        pyxel.rect(rx(w // 3), y, w // 3, h, C7)
        pyxel.rect(rx(2 * w // 3), y, w - 2 * (w // 3), h, C8)
    elif key == "russia":
        pyxel.rect(rx(0), y, w, h // 3, C7)
        pyxel.rect(rx(0), y + h // 3, w, h // 3, C12)
        pyxel.rect(rx(0), y + 2 * (h // 3), w, h - 2 * (h // 3), C8)
    elif key == "poland":
        pyxel.rect(rx(0), y, w, h // 2, C7)
        pyxel.rect(rx(0), y + h // 2, w, h - h // 2, C8)
    elif key == "france":
        pyxel.rect(rx(0), y, w // 3, h, C1)
        pyxel.rect(rx(w // 3), y, w // 3, h, C7)
        pyxel.rect(rx(2 * w // 3), y, w - 2 * (w // 3), h, C8)
    elif key == "nigeria":
        pyxel.rect(rx(0), y, w // 3, h, C3)
        pyxel.rect(rx(w // 3), y, w // 3, h, C7)
        pyxel.rect(rx(2 * w // 3), y, w - 2 * (w // 3), h, C3)
    elif key == "switzerland":
        pyxel.rect(rx(0), y, w, h, C8)
        pyxel.rect(rx(w // 2 - 1), y + 2, 2, h - 4, C7)
        pyxel.rect(rx(3), y + h // 2 - 1, w - 6, 2, C7)
    elif key == "canada":
        pyxel.rect(rx(0), y, w // 4, h, C8)
        pyxel.rect(rx(w // 4), y, w // 2, h, C7)
        pyxel.rect(rx(3 * w // 4), y, w - 3 * w // 4, h, C8)
        pyxel.pset(rx(w // 2), y + h // 2, C8)
    elif key == "ukraine":
        pyxel.rect(rx(0), y, w, h // 2, C12)
        pyxel.rect(rx(0), y + h // 2, w, h - h // 2, C10)
    elif key == "turkiye":
        pyxel.rect(rx(0), y, w, h, C8)
        pyxel.circ(rx(6), y + h // 2, 2, C7)
        pyxel.circ(rx(7), y + h // 2, 2, C8)
        pyxel.pset(rx(11), y + h // 2, C7)
    elif key == "south_korea":
        pyxel.rect(rx(0), y, w, h, C7)
        pyxel.pset(rx(w // 2 - 1), y + h // 2, C8)
        pyxel.pset(rx(w // 2), y + h // 2, C12)
    elif key == "north_korea":
        pyxel.rect(rx(0), y, w, h, C8)
        pyxel.rect(rx(0), y + 1, w, 1, C12)
        pyxel.rect(rx(0), y + h - 2, w, 1, C12)
        pyxel.pset(rx(w // 2), y + h // 2, C7)
    elif key == "saudi":
        pyxel.rect(rx(0), y, w, h, C3)
        pyxel.line(rx(3), y + h // 2, rx(w - 4), y + h // 2, C7)
    elif key == "belgium":
        pyxel.rect(rx(0), y, w // 3, h, C0)
        pyxel.rect(rx(w // 3), y, w // 3, h, C10)
        pyxel.rect(rx(2 * w // 3), y, w - 2 * (w // 3), h, C8)
    elif key == "austria":
        pyxel.rect(rx(0), y, w, h // 3, C8)
        pyxel.rect(rx(0), y + h // 3, w, h // 3, C7)
        pyxel.rect(rx(0), y + 2 * (h // 3), w, h - 2 * (h // 3), C8)
    elif key == "romania":
        pyxel.rect(rx(0), y, w // 3, h, C1)
        pyxel.rect(rx(w // 3), y, w // 3, h, C10)
        pyxel.rect(rx(2 * w // 3), y, w - 2 * (w // 3), h, C8)
    elif key == "japan":
        pyxel.rect(rx(0), y, w, h, C7)
        pyxel.circ(rx(w // 2), y + h // 2, 3, C8)
    elif key == "uk":
        pyxel.rect(rx(0), y, w, h, C1)
        pyxel.line(rx(0), y, rx(w - 1), y + h - 1, C7)
        pyxel.line(rx(w - 1), y, rx(0), y + h - 1, C7)
        pyxel.rect(rx(w // 2 - 1), y, 2, h, C8)
        pyxel.rect(rx(0), y + h // 2 - 1, w, 2, C8)
    elif key == "spain":
        pyxel.rect(rx(0), y, w, h // 4, C8)
        pyxel.rect(rx(0), y + h // 4, w, h // 2, C10)
        pyxel.rect(rx(0), y + 3 * h // 4, w, h - 3 * h // 4, C8)
    elif key == "brazil":
        pyxel.rect(rx(0), y, w, h, C3)
        pyxel.tri(rx(w // 2), y + 1, rx(2), y + h // 2, rx(w // 2), y + h - 2, C10)
        pyxel.tri(rx(w // 2), y + 1, rx(w - 2), y + h // 2, rx(w // 2), y + h - 2, C10)
        pyxel.pset(rx(w // 2), y + h // 2, C12)
    elif key == "australia":
        pyxel.rect(rx(0), y, w, h, C1)
        pyxel.rect(rx(0), y, w // 2, h // 2, C7)
        pyxel.pset(rx(w - 4), y + h // 2, C7)
    elif key == "south_africa":
        pyxel.rect(rx(0), y, w, h, C3)
        pyxel.line(rx(0), y + h // 2, rx(w - 1), y + h // 2, C10)
        pyxel.line(rx(0), y + h // 2 - 1, rx(w - 1), y + h // 2 - 1, C0)
    else:
        pyxel.rect(rx(0), y, w, h, C7)
class Terrain:
    def __init__(self):
        self.craters = []
    def reset(self):
        self.craters.clear()
    def band(self, difficulty):
        if difficulty < 2.5:
            return 0
        if difficulty < 5:
            return 1
        if difficulty < 8:
            return 2
        return 3
    def base_ground_y_at(self, x, difficulty):
        b = self.band(difficulty)
        if b == 0:
            return GROUND_Y
        if b == 1:
            return GROUND_Y - int(3 * math.sin(x * 0.018))
        if b == 2:
            return GROUND_Y - int(5 * math.sin(x * 0.020)) - int(2 * math.sin(x * 0.05))
        y = GROUND_Y - int(7 * math.sin(x * 0.021)) - int(3 * math.sin(x * 0.058))
        seg = int(x // 200) % 6
        if seg == 2:
            y += 4
        elif seg == 4:
            y -= 3
        return y
    def scripted_gap_at(self, x, difficulty):
        if difficulty < 7:
            return False
        seg = int(x // 240) % 8
        local = int(x % 240)
        return seg in (3, 6) and 84 <= local <= 116
    def crater_shape(self, x):
        max_depth = 0
        gap = False
        for c in self.craters:
            dx = abs(x - c["x"])
            r = c["r"]
            if dx < r:
                t = dx / r
                depth = int((1 - t * t) * c["depth"])
                max_depth = max(max_depth, depth)
            if dx < c["gap_r"]:
                gap = True
        return max_depth, gap
    def ground_y_at(self, x, difficulty):
        return self.base_ground_y_at(x, difficulty) + self.crater_shape(x)[0]
    def is_gap_at(self, x, difficulty):
        return self.scripted_gap_at(x, difficulty) or self.crater_shape(x)[1]
    def add_crater(self, x, radius):
        self.craters.append({
            "x": x,
            "r": radius,
            "gap_r": radius * 0.48,
            "depth": int(radius * 0.75)
        })
        if len(self.craters) > 28:
            self.craters.pop(0)
    def draw(self, cam_x, difficulty):
        for sx in range(SCREEN_W):
            wx = cam_x + sx
            base = self.base_ground_y_at(wx, difficulty)
            gy = self.ground_y_at(wx, difficulty)
            is_gap = self.is_gap_at(wx, difficulty)
            if is_gap:
                pyxel.line(sx, base, sx, gy - 1, C0)
                pyxel.line(sx, gy, sx, SCREEN_H, C4)
            else:
                pyxel.line(sx, gy, sx, SCREEN_H, C3)
        for sx in range(0, SCREEN_W, 2):
            wx = cam_x + sx
            gy = self.ground_y_at(wx, difficulty)
            if not self.is_gap_at(wx, difficulty):
                pat = (sx + int(cam_x * 0.7)) % 18
                if pat < 6:
                    pyxel.pset(sx, gy + 1, C13)
                    pyxel.pset(sx, gy + 3, C4)
                elif pat < 12:
                    pyxel.line(sx, gy + 5, sx + 1, gy + 5, C5)
                else:
                    pyxel.pset(sx, gy + 2, C4)
        for c in self.craters:
            sx = c["x"] - cam_x
            r = c["r"]
            if -r - 20 <= sx <= SCREEN_W + r + 20:
                rim_y = self.base_ground_y_at(c["x"], difficulty)
                pyxel.line(int(sx - r), rim_y, int(sx - r + 10), rim_y - 3, C4)
                pyxel.line(int(sx + r - 10), rim_y - 3, int(sx + r), rim_y, C4)
class Particle:
    def __init__(self, x, y, vx, vy, color, life, size=1, gravity=0.0):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.color = color
        self.life = life
        self.size = size
        self.gravity = gravity
        self.alive = True
    def update(self):
        self.life -= 1
        if self.life <= 0:
            self.alive = False
            return
        self.vy += self.gravity
        self.x += self.vx
        self.y += self.vy
        self.vx *= 0.99
        self.vy *= 0.99
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        if sx < -8 or sx > SCREEN_W + 8 or sy < -8 or sy > SCREEN_H + 8:
            return
        if self.size <= 1:
            pyxel.pset(sx, sy, self.color)
        else:
            pyxel.circ(sx, sy, self.size, self.color)
class Explosion:
    def __init__(self, x, y, power=12, kind="normal"):
        self.x = x
        self.y = y
        self.power = power
        self.kind = kind
        self.timer = 16 if kind != "nuke" else 28
        self.max_timer = self.timer
        self.alive = True
    def update(self):
        self.timer -= 1
        if self.timer <= 0:
            self.alive = False
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        if sx < -100 or sx > SCREEN_W + 100:
            return
        t = 1.0 - self.timer / self.max_timer
        r = max(1, int(self.power * t))
        if self.kind == "nuke":
            pyxel.circ(sx, sy, r + 14, C9)
            pyxel.circ(sx, sy, r + 8, C10)
            pyxel.circ(sx, sy, r + 3, C7)
            pyxel.circ(sx, sy - r - 10, max(3, r // 2 + 5), C10)
            pyxel.circ(sx - 12, sy - r - 5, max(2, r // 2 + 2), C9)
            pyxel.circ(sx + 12, sy - r - 5, max(2, r // 2 + 2), C9)
            pyxel.line(sx, sy + r, sx, sy - r - 8, C7)
        elif self.kind == "muzzle":
            pyxel.circ(sx, sy, r + 2, C9)
            pyxel.circ(sx, sy, r, C7)
        else:
            pyxel.circ(sx, sy, r + 4, C8)
            pyxel.circ(sx, sy, r + 2, C9)
            pyxel.circ(sx, sy, r, C10)
class FlashText:
    def __init__(self, x, y, text, color=C7, life=40):
        self.x = x
        self.y = y
        self.text = text
        self.color = color
        self.life = life
        self.alive = True
    def update(self):
        self.life -= 1
        self.y -= 0.35
        if self.life <= 0:
            self.alive = False
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        if -80 <= sx <= SCREEN_W + 80:
            pyxel.text(sx, int(self.y), self.text, self.color)
class Bullet:
    def __init__(self, x, y, vx, vy, dmg=1, color=C7, radius=2, friendly=True, life=90):
        self.x = x
        self.y = y
        self.px = x
        self.py = y
        self.vx = vx
        self.vy = vy
        self.dmg = dmg
        self.color = color
        self.radius = radius
        self.friendly = friendly
        self.life = life
        self.alive = True
    def update(self):
        self.px = self.x
        self.py = self.y
        self.x += self.vx
        self.y += self.vy
        self.life -= 1
        if self.life <= 0 or self.x < -200 or self.x > 999999 or self.y < -80 or self.y > SCREEN_H + 80:
            self.alive = False
    def draw(self, cam_x):
        sx1 = int(self.px - cam_x)
        sx2 = int(self.x - cam_x)
        pyxel.line(sx1, int(self.py), sx2, int(self.y), self.color)
        pyxel.circ(sx2, int(self.y), self.radius, self.color)
        pyxel.pset(sx2, int(self.y), C7)
class Rocket:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.vx = 3.2
        self.vy = -0.3
        self.speed = 3.8
        self.turn = 0.08
        self.life = 130
        self.alive = True
    def update(self, enemies):
        self.life -= 1
        if self.life <= 0:
            self.alive = False
            return
        best = None
        best_d = 10**12
        for e in enemies:
            if not e.alive:
                continue
            cx = e.x + e.w / 2
            cy = e.y + e.h / 2
            d = dist2(self.x, self.y, cx, cy)
            if d < best_d:
                best_d = d
                best = e
        if best:
            tx = (best.x + best.w / 2) - self.x
            ty = (best.y + best.h / 2) - self.y
            ta = math.atan2(ty, tx)
            ca = math.atan2(self.vy, self.vx)
            diff = ta - ca
            while diff > math.pi:
                diff -= math.tau
            while diff < -math.pi:
                diff += math.tau
            ca += clamp(diff, -self.turn, self.turn)
            self.vx = math.cos(ca) * self.speed
            self.vy = math.sin(ca) * self.speed
        self.x += self.vx
        self.y += self.vy
        if self.x < -100 or self.y < -100 or self.y > SCREEN_H + 100:
            self.alive = False
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        ang = math.atan2(self.vy, self.vx)
        dx = int(math.cos(ang) * 5)
        dy = int(math.sin(ang) * 5)
        pyxel.line(sx - dx, sy - dy, sx, sy, C9)
        pyxel.line(sx, sy, sx + dx, sy + dy, C10)
        pyxel.pset(sx + dx, sy + dy, C7)
class BombProjectile:
    def __init__(self, x, y, vx, vy, power=22):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.power = power
        self.alive = True
    def update(self):
        self.vy += 0.16
        self.x += self.vx
        self.y += self.vy
        if self.y > SCREEN_H + 120 or self.x < -100:
            self.alive = False
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        pyxel.circ(sx, sy, 4, C8)
        pyxel.circ(sx, sy, 2, C9)
        pyxel.pset(sx, sy - 4, C7)
class Enemy:
    def __init__(self, x, y, w, h, hp, score):
        self.x = x
        self.y = y
        self.w = w
        self.h = h
        self.hp = hp
        self.max_hp = hp
        self.score = score
        self.flash = 0
        self.alive = True
    def hurt(self, dmg):
        self.hp -= dmg
        self.flash = 4
        if self.hp <= 0:
            self.alive = False
            return True
        return False
    def update_flash(self):
        if self.flash > 0:
            self.flash -= 1
class MissileEnemy(Enemy):
    def __init__(self, x, speed):
        super().__init__(x, -20, 8, 18, 1, 12)
        self.speed = speed
        self.anim = random.randint(0, 100)
    def update(self, game):
        self.anim += 1
        self.y += self.speed
        self.update_flash()
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        body = C7 if self.flash > 0 else C9
        pyxel.rect(sx, int(self.y), self.w, self.h, body)
        pyxel.rect(sx + 2, int(self.y) - 4, 4, 4, C7)
        pyxel.line(sx + 1, int(self.y) + self.h, sx + 4, int(self.y) + self.h + 4, C8)
        pyxel.line(sx + 7, int(self.y) + self.h, sx + 4, int(self.y) + self.h + 4, C8)
        if self.anim % 4 < 2:
            pyxel.pset(sx + 4, int(self.y) + self.h + 5, C10)
class DroneEnemy(Enemy):
    def __init__(self, x, y, tx, ty, speed):
        super().__init__(x, y, 18, 10, 2, 18)
        dx = tx - x
        dy = ty - y
        l = math.sqrt(dx * dx + dy * dy)
        if l == 0:
            l = 1
        self.vx = dx / l * speed
        self.vy = dy / l * speed
        self.wave = random.random() * math.tau
    def update(self, game):
        self.wave += 0.15
        self.x += self.vx
        self.y += self.vy + math.sin(self.wave) * 0.5
        self.update_flash()
        if self.x < game.cam_x - 200 or self.y < -80 or self.y > SCREEN_H + 80:
            self.alive = False
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        body = C7 if self.flash > 0 else C15
        pyxel.rect(sx - 1, sy - 1, self.w + 2, self.h + 2, C0)
        pyxel.line(sx - 4, sy + 5, sx, sy + 4, C6)
        pyxel.line(sx + self.w, sy + 4, sx + self.w + 4, sy + 5, C6)
        pyxel.rect(sx, sy, self.w, self.h, body)
        pyxel.rect(sx + 2, sy + 1, self.w - 4, self.h - 3, C1)
        pyxel.rect(sx + 4, sy + 2, 10, 4, C12)
        pyxel.pset(sx + 6, sy + 3, C7)
        pyxel.pset(sx + 11, sy + 3, C7)
        pyxel.pset(sx + 5, sy - 1, C7)
        pyxel.pset(sx + 12, sy - 1, C7)
        pyxel.pset(sx + 4, sy + self.h, C9)
        pyxel.pset(sx + 13, sy + self.h, C9)
class TurretEnemy(Enemy):
    def __init__(self, x, y):
        super().__init__(x, y, 18, 16, 4, 28)
        self.cooldown = random.randint(25, 65)
        self.barrel_a = 0
    def update(self, game):
        px = game.player.x + 8
        py = game.player.y + 10
        self.barrel_a = math.atan2(py - (self.y - 6), px - (self.x + 9))
        self.cooldown -= 1
        self.update_flash()
        if self.cooldown <= 0:
            self.cooldown = random.randint(60, 95)
            sp = 2.6
            game.enemy_bullets.append(Bullet(
                self.x + 9, self.y - 5,
                math.cos(self.barrel_a) * sp,
                math.sin(self.barrel_a) * sp,
                dmg=1, color=C8, radius=2, friendly=False, life=120
            ))
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        body = C7 if self.flash > 0 else C4
        pyxel.rect(sx, int(self.y) - 12, 18, 12, body)
        pyxel.rect(sx + 4, int(self.y) - 16, 10, 6, C5)
        bx = sx + 9 + int(math.cos(self.barrel_a) * 10)
        by = int(self.y) - 13 + int(math.sin(self.barrel_a) * 10)
        pyxel.line(sx + 9, int(self.y) - 13, bx, by, C7)
        pyxel.rect(sx - 2, int(self.y), 22, 3, C5)
        pyxel.pset(sx + 7, int(self.y) - 8, C7)
        pyxel.pset(sx + 11, int(self.y) - 8, C7)
class HeavyBombEnemy(Enemy):
    def __init__(self, x, y, speed):
        super().__init__(x, y, 18, 32, 4, 40)
        self.speed = speed
    def update(self, game):
        self.y += self.speed
        self.update_flash()
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        body = C7 if self.flash > 0 else C8
        pyxel.rect(sx, int(self.y), self.w, self.h, body)
        pyxel.rect(sx + 4, int(self.y) - 5, 10, 5, C7)
        pyxel.line(sx + 2, int(self.y) + self.h, sx + 9, int(self.y) + self.h + 6, C9)
        pyxel.line(sx + 16, int(self.y) + self.h, sx + 9, int(self.y) + self.h + 6, C9)
        pyxel.rect(sx + 6, int(self.y) + 10, 6, 8, C10)
class BomberEnemy(Enemy):
    def __init__(self, x, y, direction, drop_x):
        super().__init__(x, y, 154, 36, 12, 120)
        self.dir = direction
        self.vx = 1.25 * direction
        self.drop_x = drop_x
        self.drop_cd = 120
        self.has_dropped = False
        self.anim = 0
        self.warning_phase = False
    def update(self, game):
        self.anim += 1
        self.x += self.vx
        self.update_flash()
        if not self.has_dropped and abs((self.x + self.w // 2) - self.drop_x) < 90:
            self.warning_phase = True
            self.drop_cd -= 1
            if self.drop_cd <= 0:
                self.has_dropped = True
                game.enemies.append(HeavyBombEnemy(self.x + self.w // 2 - 9, self.y + 24, 2.0))
        if self.x < game.cam_x - 300 or self.x > game.cam_x + SCREEN_W + 300:
            self.alive = False
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        body = C7 if self.flash > 0 else C12
        pyxel.rect(sx + 28, sy + 12, 100, 12, body)
        pyxel.rect(sx + 20, sy + 14, 116, 8, C6)
        if self.dir == 1:
            pyxel.tri(sx + 128, sy + 12, sx + 154, sy + 18, sx + 128, sy + 24, body)
            pyxel.rect(sx + 136, sy + 15, 8, 4, C7)
        else:
            pyxel.tri(sx + 28, sy + 12, sx + 2, sy + 18, sx + 28, sy + 24, body)
            pyxel.rect(sx + 10, sy + 15, 8, 4, C7)
        pyxel.tri(sx + 52, sy + 14, sx + 14, sy + 38, sx + 70, sy + 20, C1)
        pyxel.tri(sx + 104, sy + 14, sx + 142, sy + 38, sx + 86, sy + 20, C1)
        pyxel.tri(sx + 62, sy + 14, sx + 28, sy - 8, sx + 78, sy + 12, C1)
        pyxel.tri(sx + 94, sy + 14, sx + 128, sy - 8, sx + 78, sy + 12, C1)
        for ex in (54, 72, 90, 108):
            pyxel.rect(sx + ex, sy + 24, 8, 5, C4)
            if self.anim % 4 < 2:
                pyxel.pset(sx + ex + 3, sy + 30, C9)
        if self.warning_phase and not self.has_dropped:
            n = max(1, self.drop_cd // 10 + 1)
            pyxel.rect(sx + 63, sy - 14, 24, 10, C0)
            pyxel.rectb(sx + 63, sy - 14, 24, 10, C8)
            pyxel.text(sx + 71, sy - 11, str(n), C10)
            if (pyxel.frame_count // 6) % 2 == 0:
                pyxel.text(sx + 54, sy - 24, "NUKE", C8)
class BossEnemy(Enemy):
    def __init__(self, x):
        super().__init__(x, 64, 76, 74, 80, 650)
        self.fire_cd = 34
        self.missile_cd = 110
        self.dir = -1
        self.wave = 0
    def update(self, game):
        self.wave += 0.04
        self.y = 64 + math.sin(self.wave) * 8
        self.x += self.dir * 0.7
        if self.x < game.player.x + 70:
            self.dir = 1
        if self.x > game.player.x + 190:
            self.dir = -1
        self.fire_cd -= 1
        self.missile_cd -= 1
        self.update_flash()
        if self.fire_cd <= 0:
            self.fire_cd = 16 if self.hp < 30 else 26
            for off in (-0.20, -0.08, 0.08, 0.20):
                dx = (game.player.x + 8) - (self.x + 38)
                dy = (game.player.y + 10) - (self.y + 24)
                ang = math.atan2(dy, dx) + off
                game.enemy_bullets.append(Bullet(
                    self.x + 38, self.y + 24,
                    math.cos(ang) * 3.2,
                    math.sin(ang) * 3.2,
                    dmg=1, color=C8, radius=2, friendly=False, life=150
                ))
        if self.missile_cd <= 0:
            self.missile_cd = 70 if self.hp < 30 else 105
            count = 4 if self.hp < 35 else 2
            for _ in range(count):
                mx = self.x + random.randint(0, self.w)
                game.enemies.append(MissileEnemy(mx, 1.9 + random.random() * 0.8))
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        body = C7 if self.flash > 0 else C5
        bright = C12 if self.flash == 0 else C7
        pyxel.rect(sx + 18, sy + 52, 12, 20, C4)
        pyxel.rect(sx + 46, sy + 52, 12, 20, C4)
        pyxel.rect(sx + 14, sy + 70, 18, 4, C0)
        pyxel.rect(sx + 44, sy + 70, 18, 4, C0)
        pyxel.rect(sx + 16, sy + 14, 44, 38, body)
        pyxel.rect(sx + 10, sy + 22, 56, 28, C1)
        pyxel.rect(sx, sy + 18, 16, 14, bright)
        pyxel.rect(sx + 60, sy + 18, 16, 14, bright)
        pyxel.rect(sx + 2, sy + 10, 12, 8, C8)
        pyxel.rect(sx + 62, sy + 10, 12, 8, C8)
        pyxel.pset(sx + 5, sy + 13, C10)
        pyxel.pset(sx + 9, sy + 13, C10)
        pyxel.pset(sx + 65, sy + 13, C10)
        pyxel.pset(sx + 69, sy + 13, C10)
        pyxel.rect(sx + 24, sy, 28, 16, body)
        pyxel.rect(sx + 26, sy + 2, 24, 12, C1)
        pyxel.rect(sx + 29, sy + 6, 6, 3, C8)
        pyxel.rect(sx + 41, sy + 6, 6, 3, C8)
        pyxel.circ(sx + 38, sy + 30, 8, C8)
        pyxel.circ(sx + 38, sy + 30, 5, C10)
        pyxel.circ(sx + 38, sy + 30, 2 + (pyxel.frame_count // 8) % 2, C7)
        pyxel.line(sx + 10, sy + 32, sx - 10, sy + 42, C12)
        pyxel.line(sx + 66, sy + 32, sx + 86, sy + 42, C12)
        pyxel.rect(sx - 14, sy + 40, 8, 8, C8)
        pyxel.rect(sx + 82, sy + 40, 8, 8, C8)
        pyxel.rect(70, 8, 180, 8, C0)
        fill = int(178 * self.hp / self.max_hp)
        pyxel.rect(71, 9, fill, 6, C8)
        pyxel.rectb(70, 8, 180, 8, C7)
        pyxel.text(138, 1, "BOSS: NATIONAL DESTROYER", C7)
class PowerUp:
    TYPES = ["rapid", "shield", "spread", "rocket", "bomb", "overdrive", "super_shield"]
    def __init__(self, x, y, kind=None):
        self.x = x
        self.y = y
        self.kind = kind or random.choice(self.TYPES)
        self.vy = 0
        self.alive = True
        self.anim = 0
        self.w = 12
        self.h = 12
    def update(self, terrain, difficulty):
        self.anim += 1
        self.vy += 0.16
        self.y += self.vy
        gy = terrain.ground_y_at(self.x + 6, difficulty)
        if not terrain.is_gap_at(self.x + 6, difficulty) and self.y >= gy - 6:
            self.y = gy - 6
            self.vy = -self.vy * 0.25
            if abs(self.vy) < 0.3:
                self.vy = 0
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        sy = int(self.y + math.sin(self.anim * 0.15) * 1.5)
        col = {
            "rapid": C9,
            "shield": C12,
            "spread": C15,
            "rocket": C10,
            "bomb": C8,
            "overdrive": C14,
            "super_shield": C7,
        }[self.kind]
        pyxel.circb(sx + 5, sy + 5, 8 + (pyxel.frame_count // 8) % 2, col)
        pyxel.rect(sx + 1, sy + 1, 10, 10, col)
        pyxel.rectb(sx, sy, 12, 12, C7)
        pyxel.pset(sx + 6, sy + 6, C7)
class Coin:
    def __init__(self, x, y, value=1):
        self.x = x
        self.y = y
        self.vx = random.uniform(-1.0, 1.0)
        self.vy = random.uniform(-2.0, -0.5)
        self.value = value
        self.alive = True
        self.life = 600
        self.anim = 0
    def update(self, game):
        self.anim += 1
        self.life -= 1
        if self.life <= 0:
            self.alive = False
            return
        self.vy += 0.10
        self.x += self.vx
        self.y += self.vy
        self.vx *= 0.98
        px = game.player.x + 8
        py = game.player.y + 10
        dx = px - self.x
        dy = py - self.y
        d = math.sqrt(dx * dx + dy * dy) if (dx != 0 or dy != 0) else 1
        magnet_r = 28 + game.player.coin_magnet * 16
        if d < magnet_r:
            self.x += dx / d * 1.6
            self.y += dy / d * 1.6
        gy = game.terrain.ground_y_at(self.x, game.difficulty_value())
        if not game.terrain.is_gap_at(self.x, game.difficulty_value()) and self.y >= gy - 3:
            self.y = gy - 3
            self.vy *= -0.25
            if abs(self.vy) < 0.25:
                self.vy = 0
        if dist2(self.x, self.y, px, py) < 12 * 12:
            self.alive = False
            game.player.run_coins += self.value
            game.coins_total += self.value
            game.add_text(self.x, self.y - 6, "+" + str(self.value) + "$", C10)
            sfx(0, 3)
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        sy = int(self.y + math.sin(self.anim * 0.18) * 1.5)
        pyxel.circ(sx, sy, 3, C10)
        pyxel.pset(sx, sy, C7)
class Player:
    SKINS = [
        {"name": "COMMANDER", "body": C12, "cape": C8, "visor": C14},
        {"name": "WARLORD", "body": C5, "cape": C2, "visor": C12},
        {"name": "IMPERATOR", "body": C14, "cape": C8, "visor": C7},
        {"name": "NEON GENERAL", "body": C15, "cape": C12, "visor": C10},
        {"name": "IRON MARSHAL", "body": C6, "cape": C1, "visor": C8},
        {"name": "PHANTOM KING", "body": C0, "cape": C2, "visor": C15},
        {"name": "SUN EMPEROR", "body": C10, "cape": C8, "visor": C14},
        {"name": "FROST TYRANT", "body": C7, "cape": C12, "visor": C1},
    ]
    WEAPONS = ["BLASTER", "SPREAD", "LASER"]
    def __init__(self, terrain, skin_index=0, country_index=0, extra_hp_bonus=0):
        self.terrain = terrain
        self.skin_index = skin_index
        self.country_index = country_index
        self.fire_rate_bonus = 0
        self.max_hp_bonus = extra_hp_bonus
        self.coin_magnet = 0
        self.reset(40, 1)
    @property
    def skin(self):
        return self.SKINS[self.skin_index]
    @property
    def country(self):
        return COUNTRIES[self.country_index]
    @property
    def weapon(self):
        if self.spread_timer > 0:
            return "SPREAD"
        return self.WEAPONS[self.weapon_index]
    def hurtbox(self):
        return self.x + 2, self.y + 2, PLAYER_W - 4, PLAYER_H - 3
    def protected(self):
        return self.invuln > 0
    def add_score(self, pts):
        bonus = self.combo * 2
        self.score += pts + bonus
        self.combo += 1
        self.combo_timer = 120
    def take_damage(self, dmg):
        if self.protected():
            return False
        self.hp -= dmg
        self.invuln = 70
        self.hurt_flash = 10
        self.combo = 0
        self.combo_timer = 0
        return True
    def reset(self, x, difficulty):
        self.x = x
        self.y = self.terrain.ground_y_at(x + 8, difficulty) - PLAYER_H
        self.vx = 0
        self.vy = 0
        self.facing = 1
        self.on_ground = True
        self.max_hp = 5 + self.max_hp_bonus
        self.hp = self.max_hp
        self.invuln = 100
        self.speed = 1.9
        self.air_speed = 1.6
        self.jump_power = -5.5
        self.jump_count = 0
        self.max_jumps = 2
        self.coyote_timer = 0
        self.jump_buffer = 0
        self.dash_timer = 0
        self.dash_cd = 0
        self.dash_dir = 1
        self.weapon_index = 0
        self.shot_cd = 0
        self.shoot_anim = 0
        self.hurt_flash = 0
        self.walk_frame = 0
        self.rapid_timer = 0
        self.spread_timer = 0
        self.overdrive_timer = 0
        self.super_shield_timer = 0
        self.rockets = 3
        self.bombs = 2
        self.special_cd = 0
        self.score = 0
        self.combo = 0
        self.combo_timer = 0
        self.run_coins = 0
    def update(self, difficulty, dev_mode=False):
        if self.invuln > 0:
            self.invuln -= 1
        if self.shot_cd > 0:
            self.shot_cd -= 1
        if self.shoot_anim > 0:
            self.shoot_anim -= 1
        if self.hurt_flash > 0:
            self.hurt_flash -= 1
        if self.dash_cd > 0:
            self.dash_cd -= 1
        if self.dash_timer > 0:
            self.dash_timer -= 1
        if self.rapid_timer > 0:
            self.rapid_timer -= 1
        if self.spread_timer > 0:
            self.spread_timer -= 1
        if self.overdrive_timer > 0:
            self.overdrive_timer -= 1
        if self.super_shield_timer > 0:
            self.super_shield_timer -= 1
        if self.special_cd > 0:
            self.special_cd -= 1
        if self.combo_timer > 0:
            self.combo_timer -= 1
            if self.combo_timer <= 0:
                self.combo = 0
        if self.on_ground:
            self.coyote_timer = 6
        elif self.coyote_timer > 0:
            self.coyote_timer -= 1
        if self.jump_buffer > 0:
            self.jump_buffer -= 1
        if pyxel.btnp(pyxel.KEY_W):
            self.jump_buffer = 6
        if pyxel.btnp(pyxel.KEY_SHIFT):
            self.weapon_index = (self.weapon_index + 1) % len(self.WEAPONS)
            sfx(1, 4)
        move = self.speed if self.on_ground else self.air_speed
        if pyxel.btnp(pyxel.KEY_SPACE) and self.dash_cd <= 0:
            if pyxel.btn(pyxel.KEY_A):
                self.dash_dir = -1
            elif pyxel.btn(pyxel.KEY_D):
                self.dash_dir = 1
            else:
                self.dash_dir = self.facing
            self.dash_timer = 8
            self.dash_cd = 30
            self.invuln = max(self.invuln, 8)
            sfx(1, 4)
        if self.dash_timer > 0:
            self.vx = self.dash_dir * 4.8
        else:
            self.vx = 0
            if pyxel.btn(pyxel.KEY_A):
                self.vx = -move
                self.facing = -1
            if pyxel.btn(pyxel.KEY_D):
                self.vx = move
                self.facing = 1
        if self.jump_buffer > 0:
            if self.on_ground or self.coyote_timer > 0:
                self.vy = self.jump_power
                self.on_ground = False
                self.jump_count = 1
                self.coyote_timer = 0
                self.jump_buffer = 0
            elif self.jump_count < self.max_jumps:
                self.vy = self.jump_power + 0.2
                self.jump_count += 1
                self.jump_buffer = 0
        if (not self.on_ground) and pyxel.btn(pyxel.KEY_S):
            self.vy += 0.45
        self.vy = min(self.vy + GRAVITY, MAX_FALL)
        self.x += self.vx
        self.y += self.vy
        if self.x < 0:
            self.x = 0
        foot_l = self.x + 2
        foot_m = self.x + PLAYER_W / 2
        foot_r = self.x + PLAYER_W - 2
        gaps = [
            self.terrain.is_gap_at(foot_l, difficulty),
            self.terrain.is_gap_at(foot_m, difficulty),
            self.terrain.is_gap_at(foot_r, difficulty),
        ]
        grounds = [
            self.terrain.ground_y_at(foot_l, difficulty),
            self.terrain.ground_y_at(foot_m, difficulty),
            self.terrain.ground_y_at(foot_r, difficulty),
        ]
        supported = (not gaps[1]) or ((not gaps[0]) and (not gaps[2]))
        target_ground = min(grounds)
        if supported and self.y >= target_ground - PLAYER_H:
            self.y = target_ground - PLAYER_H
            self.vy = 0
            self.on_ground = True
            self.jump_count = 0
            self.coyote_timer = 6
        else:
            self.on_ground = False
        if self.on_ground and abs(self.vx) > 0.1:
            self.walk_frame += 1
        else:
            self.walk_frame = 0
        if dev_mode:
            self.hp = self.max_hp
            self.rockets = 99
            self.bombs = 99
            self.invuln = max(self.invuln, 2)
    def fire(self):
        if self.shot_cd > 0:
            return [], []
        base_rate = 8 if self.rapid_timer > 0 else 11
        if self.weapon == "SPREAD":
            base_rate += 4
        if self.overdrive_timer > 0:
            base_rate -= 3
        rate = max(4, base_rate - self.fire_rate_bonus)
        self.shot_cd = rate
        self.shoot_anim = 5
        px = self.x + 8
        py = self.y + 10
        if pyxel.btn(pyxel.KEY_LEFT):
            dx, dy = -1, 0
            self.facing = -1
        elif pyxel.btn(pyxel.KEY_RIGHT):
            dx, dy = 1, 0
            self.facing = 1
        elif pyxel.btn(pyxel.KEY_UP):
            dx, dy = 0, -1
        elif pyxel.btn(pyxel.KEY_DOWN):
            dx, dy = 0, 1
        else:
            dx, dy = self.facing, 0
        ang = math.atan2(dy, dx)
        bullets = []
        effects = [Explosion(px + dx * 6, py + dy * 2, 4, "muzzle")]
        if self.weapon == "BLASTER":
            for off in (-0.04, 0.0, 0.04):
                a = ang + off
                bullets.append(Bullet(
                    px, py,
                    math.cos(a) * 7.0,
                    math.sin(a) * 7.0,
                    dmg=2 if self.overdrive_timer > 0 else 1,
                    color=C10,
                    radius=2,
                    friendly=True,
                    life=95
                ))
        elif self.weapon == "SPREAD":
            for off in (-0.18, -0.09, 0, 0.09, 0.18):
                a = ang + off
                bullets.append(Bullet(
                    px, py,
                    math.cos(a) * 5.8,
                    math.sin(a) * 5.8,
                    dmg=1,
                    color=C15,
                    radius=2,
                    friendly=True,
                    life=76
                ))
        else:
            bullets.append(Bullet(
                px, py,
                math.cos(ang) * 9.2,
                math.sin(ang) * 9.2,
                dmg=3 if self.overdrive_timer > 0 else 2,
                color=C12,
                radius=2,
                friendly=True,
                life=75
            ))
            bullets.append(Bullet(
                px, py,
                math.cos(ang) * 8.6,
                math.sin(ang) * 8.6,
                dmg=1,
                color=C7,
                radius=1,
                friendly=True,
                life=55
            ))
        sfx(0, 0)
        return bullets, effects
    def fire_rocket(self):
        if self.rockets <= 0 or self.special_cd > 0:
            return None
        self.rockets -= 1
        self.special_cd = 16
        sfx(0, 5)
        return Rocket(self.x + 10, self.y + 8)
    def fire_bomb(self):
        if self.bombs <= 0 or self.special_cd > 0:
            return None
        self.bombs -= 1
        self.special_cd = 18
        return BombProjectile(self.x + 8, self.y + 6, self.facing * 2.2, -3.2, 22)
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        if self.hurt_flash > 0 and (pyxel.frame_count // 2) % 2 == 0:
            return
        body = self.skin["body"]
        cape = self.skin["cape"]
        visor = self.skin["visor"]
        dark_body = max(1, body - 1)
        bright_body = min(15, body + 1)
        step = 0
        if self.on_ground and abs(self.vx) > 0.1:
            step = 1 if (self.walk_frame // 5) % 2 == 0 else -1
        if self.invuln > 0 and (pyxel.frame_count // 4) % 2 == 0:
            col = C7 if self.super_shield_timer > 0 else C12
            pyxel.circb(sx + 8, sy + 11, 13, col)
            pyxel.circb(sx + 8, sy + 11, 11, C6)
        if self.dash_timer > 0:
            for i in range(1, 5):
                ox = sx - self.facing * i * 5
                pole_x = ox + 19 if self.facing == 1 else ox - 7
                pyxel.line(pole_x, sy + 4, pole_x, sy + 18, C5)
                draw_flag(self.country["key"], pole_x + (1 if self.facing == 1 else -13), sy + 5, 10, 6, pyxel.frame_count * 0.3 + i)
        cape_wave = 1 if (pyxel.frame_count // 4) % 2 == 0 else -1
        pyxel.tri(sx + 4, sy + 8, sx - 4, sy + 21 + cape_wave, sx + 5, sy + 20, cape)
        pyxel.tri(sx + 12, sy + 8, sx + 20, sy + 21 - cape_wave, sx + 11, sy + 20, cape)
        pyxel.line(sx + 6, sy + 8, sx + 3, sy + 20, dark_body)
        pyxel.line(sx + 10, sy + 8, sx + 13, sy + 20, dark_body)
        pole_x = sx + 19 if self.facing == 1 else sx - 7
        pyxel.line(pole_x, sy + 2, pole_x, sy + 22, C4)
        if self.facing == 1:
            draw_flag(self.country["key"], pole_x + 1, sy + 3, 14, 8, pyxel.frame_count * 0.25)
        else:
            draw_flag(self.country["key"], pole_x - 15, sy + 3, 14, 8, pyxel.frame_count * 0.25)
        pyxel.rect(sx + 3, sy - 1, 10, 7, C0)
        pyxel.rect(sx + 1, sy + 5, 14, 13, C0)
        pyxel.rect(sx + 4, sy, 8, 5, C11)
        pyxel.rect(sx + 3, sy + 1, 10, 3, C11)
        pyxel.rect(sx + 3, sy - 2, 10, 2, visor)
        pyxel.pset(sx + 7, sy - 3, C14)
        pyxel.pset(sx + 8, sy - 3, C14)
        if self.facing == 1:
            pyxel.rect(sx + 7, sy + 2, 4, 2, visor)
            pyxel.pset(sx + 10, sy + 2, C7)
        else:
            pyxel.rect(sx + 5, sy + 2, 4, 2, visor)
            pyxel.pset(sx + 5, sy + 2, C7)
        pyxel.rect(sx + 1, sy + 6, 4, 4, bright_body)
        pyxel.rect(sx + 11, sy + 6, 4, 4, bright_body)
        pyxel.line(sx + 1, sy + 6, sx + 4, sy + 5, C7)
        pyxel.line(sx + 11, sy + 6, sx + 14, sy + 5, C7)
        pyxel.rect(sx + 4, sy + 6, 8, 11, body)
        pyxel.rect(sx + 5, sy + 7, 6, 9, bright_body)
        pyxel.line(sx + 8, sy + 6, sx + 8, sy + 16, dark_body)
        pyxel.line(sx + 4, sy + 7, sx + 11, sy + 15, C8)
        pyxel.line(sx + 5, sy + 7, sx + 12, sy + 14, C14)
        pyxel.pset(sx + 10, sy + 10, C14)
        pyxel.pset(sx + 10, sy + 11, C10)
        pyxel.pset(sx + 6, sy + 11, C7)
        pyxel.rect(sx + 4, sy + 16, 8, 2, C4)
        pyxel.pset(sx + 8, sy + 16, C14)
        if self.shoot_anim > 0:
            if self.facing == 1:
                pyxel.line(sx + 12, sy + 9, sx + 16, sy + 9, bright_body)
                pyxel.line(sx + 4, sy + 9, sx + 1, sy + 12, dark_body)
            else:
                pyxel.line(sx + 4, sy + 9, sx, sy + 9, bright_body)
                pyxel.line(sx + 12, sy + 9, sx + 15, sy + 12, dark_body)
        else:
            pyxel.line(sx + 4, sy + 9, sx + 1, sy + 13, dark_body)
            pyxel.line(sx + 12, sy + 9, sx + 15, sy + 13, dark_body)
        pyxel.rect(sx + 5, sy + 18, 3, 4, dark_body)
        pyxel.rect(sx + 9, sy + 18, 3, 4, dark_body)
        pyxel.line(sx + 6, sy + 22, sx + 5 + step, sy + 25, C5)
        pyxel.line(sx + 10, sy + 22, sx + 11 - step, sy + 25, C5)
        pyxel.rect(sx + 3 + step, sy + 24, 4, 2, C0)
        pyxel.rect(sx + 9 - step, sy + 24, 4, 2, C0)
        if self.facing == 1:
            pyxel.rect(sx + 14, sy + 8, 4, 2, C5)
            pyxel.rect(sx + 17, sy + 8, 3, 1, C7)
        else:
            pyxel.rect(sx - 2, sy + 8, 4, 2, C5)
            pyxel.rect(sx - 3, sy + 8, 3, 1, C7)
        if self.shoot_anim > 0:
            mx = sx + 20 if self.facing == 1 else sx - 3
            my = sy + 9
            pyxel.circ(mx, my, 3, C9)
            pyxel.circ(mx, my, 2, C10)
            pyxel.pset(mx, my, C7)
class Game:
    def __init__(self):
        pyxel.init(SCREEN_W, SCREEN_H, title="Supreme Leader Survival", fps=FPS)
        setup_audio()
        self.save_data = load_save()
        self.terrain = Terrain()
        self.mode_names = ["CAMPAIGN", "SURVIVAL", "MULTIPLAYER"]
        self.mode_index = 0
        self.skin_index = 0
        self.country_index = self.save_data["selected_country"]
        self.state = "intro_country"
        self.intro_timer = 0
        self.intro_flash = 0
        self.intro_bombs = []
        self.dev_mode = False
        self.dev_buffer = ""
        self.tutorial_step = 0
        self.pause_index = 0
        self.story_progress_index = 0
        self.mp_mode_index = 0
        self.mp_mode_names = ["SURVIVAL ROTATION", "CAMPAIGN ROTATION"]
        self.mp_player_count = 2
        self.mp_current_player = 0
        self.mp_scores = []
        self.mp_segment_progress = 0
        self.mp_campaign_segments_left = 0
        self.level = 1
        self.distance = 0
        self.total_distance = 0
        self.checkpoint_x = 40
        self.checkpoint_level = 1
        self.survival_frames = 0
        self.best_survival = self.save_data["best_survival"]
        self.level_clear_timer = 0
        self.coins_total = self.save_data["coins_total"]
        self.unlocked_skins = self.save_data["unlocked_skins"]
        self.extra_hp_bonus = self.save_data["extra_hp_bonus"]
        self.player = Player(self.terrain, self.skin_index, self.country_index, self.extra_hp_bonus)
        self.player_bullets = []
        self.enemy_bullets = []
        self.rockets = []
        self.bombs = []
        self.enemies = []
        self.effects = []
        self.particles = []
        self.texts = []
        self.powerups = []
        self.coins = []
        self.cam_x = 0
        self.shake_timer = 0
        self.shake_power = 0
        self.shake_x = 0
        self.shake_y = 0
        self.stars = []
        for i in range(85):
            self.stars.append({
                "x": random.randint(0, 4000),
                "y": random.randint(0, 90),
                "color": C7 if i % 5 == 0 else C6,
                "speed": random.choice([0.10, 0.15, 0.22]),
            })
        pyxel.run(self.update, self.draw)
    def current_country(self):
        return COUNTRIES[self.country_index]
    def is_campaign(self):
        return self.mode_names[self.mode_index] == "CAMPAIGN"
    def is_survival(self):
        return self.mode_names[self.mode_index] == "SURVIVAL"
    def is_multiplayer(self):
        return self.mode_names[self.mode_index] == "MULTIPLAYER"
    def progress_ratio(self):
        return clamp(self.distance / LEVEL_DISTANCE, 0, 1)
    def difficulty_value(self):
        if self.is_campaign() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"):
            return self.level + self.progress_ratio() * 2.2
        return 1 + self.survival_frames / 750.0
    def danger_scale(self):
        if self.is_campaign() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"):
            return 1 + (self.level - 1) * 0.24 + self.progress_ratio() * 1.30
        return 1 + self.survival_frames / 650.0
    def reset_run(self):
        self.level = 1
        self.distance = 0
        self.total_distance = 0
        self.checkpoint_x = 40
        self.checkpoint_level = 1
        self.survival_frames = 0
        self.terrain.reset()
        old_player = self.player
        self.player = Player(self.terrain, self.skin_index, self.country_index, self.extra_hp_bonus)
        self.player.fire_rate_bonus = old_player.fire_rate_bonus
        self.player.max_hp_bonus = max(old_player.max_hp_bonus, self.extra_hp_bonus)
        self.player.coin_magnet = old_player.coin_magnet
        self.player.reset(self.checkpoint_x, self.difficulty_value())
        self.player.max_hp_bonus = max(self.player.max_hp_bonus, self.extra_hp_bonus)
        self.player.max_hp = 5 + self.player.max_hp_bonus
        self.player.hp = self.player.max_hp
        self.player_bullets.clear()
        self.enemy_bullets.clear()
        self.rockets.clear()
        self.bombs.clear()
        self.enemies.clear()
        self.effects.clear()
        self.particles.clear()
        self.texts.clear()
        self.powerups.clear()
        self.coins.clear()
        self.cam_x = 0
        self.state = "tutorial"
        stop_story_music()
    def reset_multiplayer_session(self):
        self.mp_scores = [0 for _ in range(self.mp_player_count)]
        self.mp_current_player = 0
        self.mp_segment_progress = 0
        self.mp_campaign_segments_left = CAMPAIGN_LEVELS
        self.reset_run()
    def respawn_or_game_over(self):
        self.spawn_explosion(self.player.x + 8, self.player.y + 10, 18, "normal")
        self.add_shake(12, 4)
        if self.is_campaign():
            self.level = self.checkpoint_level
            self.distance = 0
            self.terrain.reset()
            self.player_bullets.clear()
            self.enemy_bullets.clear()
            self.rockets.clear()
            self.bombs.clear()
            self.enemies.clear()
            self.effects.clear()
            self.particles.clear()
            self.texts.clear()
            self.powerups.clear()
            self.coins.clear()
            self.player.reset(self.checkpoint_x, self.difficulty_value())
            self.player.max_hp_bonus = max(self.player.max_hp_bonus, self.extra_hp_bonus)
            self.player.max_hp = 5 + self.player.max_hp_bonus
            self.player.hp = self.player.max_hp
        elif self.is_multiplayer():
            self.mp_scores[self.mp_current_player] += self.player.score
            self.mp_current_player = (self.mp_current_player + 1) % self.mp_player_count
            self.player_bullets.clear()
            self.enemy_bullets.clear()
            self.rockets.clear()
            self.bombs.clear()
            self.enemies.clear()
            self.effects.clear()
            self.particles.clear()
            self.texts.clear()
            self.powerups.clear()
            self.coins.clear()
            self.terrain.reset()
            if self.mp_mode_names[self.mp_mode_index] == "SURVIVAL ROTATION":
                self.player.reset(self.checkpoint_x, self.difficulty_value())
            else:
                self.player.reset(self.checkpoint_x + self.mp_segment_progress, self.difficulty_value())
            self.player.max_hp_bonus = max(self.player.max_hp_bonus, self.extra_hp_bonus)
            self.player.max_hp = 5 + self.player.max_hp_bonus
            self.player.hp = self.player.max_hp
            self.add_text(self.player.x + 10, 24, "PLAYER " + str(self.mp_current_player + 1), C14)
        else:
            self.best_survival = max(self.best_survival, self.survival_frames // 60)
            self.save_data["best_survival"] = self.best_survival
            self.save_data["coins_total"] = self.coins_total
            self.save_data["unlocked_skins"] = self.unlocked_skins
            self.save_data["selected_country"] = self.country_index
            self.save_data["extra_hp_bonus"] = self.extra_hp_bonus
            save_save(self.save_data)
            self.state = "game_over"
            play_story_music()
    def next_level(self):
        self.level += 1
        self.distance = 0
        self.checkpoint_level = self.level
        self.checkpoint_x = self.player.x + 30
        self.player_bullets.clear()
        self.enemy_bullets.clear()
        self.rockets.clear()
        self.bombs.clear()
        self.enemies.clear()
        self.effects.clear()
        self.particles.clear()
        self.texts.clear()
        self.powerups.clear()
        self.coins.clear()
        self.terrain.reset()
        if self.level > CAMPAIGN_LEVELS:
            self.state = "victory"
            play_story_music()
        else:
            self.player.reset(self.checkpoint_x, self.difficulty_value())
            self.player.max_hp_bonus = max(self.player.max_hp_bonus, self.extra_hp_bonus)
            self.player.max_hp = 5 + self.player.max_hp_bonus
            self.player.hp = self.player.max_hp
            self.story_progress_index += 1
            self.state = "story_update"
            self.intro_timer = 0
            self.intro_bombs = []
            play_story_music()
    def add_shake(self, timer, power):
        self.shake_timer = max(self.shake_timer, timer)
        self.shake_power = max(self.shake_power, power)
    def update_shake(self):
        if self.shake_timer > 0:
            self.shake_timer -= 1
            self.shake_x = random.randint(-self.shake_power, self.shake_power)
            self.shake_y = random.randint(-self.shake_power, self.shake_power)
        else:
            self.shake_x = 0
            self.shake_y = 0
            self.shake_power = 0
    def spawn_explosion(self, x, y, power=12, kind="normal"):
        self.effects.append(Explosion(x, y, power, kind))
        count = power + 4
        for _ in range(count):
            ang = random.random() * math.tau
            sp = random.uniform(0.6, 2.8 if kind != "nuke" else 4.0)
            col = random.choice([C8, C9, C10, C7])
            self.particles.append(Particle(
                x, y,
                math.cos(ang) * sp,
                math.sin(ang) * sp - random.random() * 0.5,
                col, random.randint(10, 24),
                size=random.choice([1, 1, 2]),
                gravity=0.05
            ))
    def spawn_smoke(self, x, y, amount=3):
        for _ in range(amount):
            self.particles.append(Particle(
                x + random.randint(-2, 2),
                y + random.randint(-2, 2),
                random.uniform(-0.5, 0.5),
                random.uniform(-1.1, -0.3),
                random.choice([C5, C6, C2]),
                random.randint(12, 24),
                size=random.choice([1, 2]),
                gravity=-0.01
            ))
    def add_text(self, x, y, text, color=C7):
        self.texts.append(FlashText(x, y, text, color))
    def update_dev_buffer(self):
        keys = [
            (pyxel.KEY_D, "D"),
            (pyxel.KEY_E, "E"),
            (pyxel.KEY_V, "V"),
        ]
        for key, ch in keys:
            if pyxel.btnp(key):
                self.dev_buffer += ch
                if len(self.dev_buffer) > 8:
                    self.dev_buffer = self.dev_buffer[-8:]
        if "DEV" in self.dev_buffer:
            self.dev_mode = not self.dev_mode
            self.dev_buffer = ""
            self.add_text(self.player.x + 30, 30, "DEV ON" if self.dev_mode else "DEV OFF", C14 if self.dev_mode else C8)
    def missile_spawn_chance(self):
        d = self.danger_scale()
        return min(0.006 + d * 0.005, 0.12)
    def drone_spawn_chance(self):
        d = self.danger_scale()
        return min(0.002 + d * 0.0026, 0.04)
    def turret_spawn_chance(self):
        d = self.difficulty_value()
        if d < 2.2:
            return 0
        return min(0.001 + d * 0.0014, 0.012)
    def bomber_spawn_chance(self):
        d = self.difficulty_value()
        if d < 4.8:
            return 0
        return min(0.00035 + d * 0.00022, 0.0024)
    def maybe_spawn_enemies(self):
        boss_alive = False
        for e in self.enemies:
            if isinstance(e, BossEnemy):
                boss_alive = True
                break
        is_campaign_like = self.is_campaign() or (
            self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"
        )
        if is_campaign_like and not boss_alive and self.level in (3, 6, 9) and self.progress_ratio() > 0.76:
            self.enemies.append(BossEnemy(self.player.x + 240))
            self.add_text(self.player.x + 80, 36, "BOSS INCOMING", C8)
            sfx(0, 7)
            return
        if boss_alive:
            return
        if random.random() < self.bomber_spawn_chance():
            direction = random.choice([-1, 1])
            x = self.cam_x - 180 if direction == 1 else self.cam_x + SCREEN_W + 40
            y = random.randint(22, 46)
            drop_x = self.player.x + random.randint(80, 200)
            self.enemies.append(BomberEnemy(x, y, direction, drop_x))
            self.add_text(self.player.x + 120, 26, "STRATEGIC BOMBER", C8)
        if random.random() < self.missile_spawn_chance():
            x = self.cam_x + random.randint(0, SCREEN_W - 10)
            sp = 1.3 + self.danger_scale() * 0.42 + random.random() * 0.4
            self.enemies.append(MissileEnemy(x, sp))
        if random.random() < self.drone_spawn_chance():
            side = random.choice([-1, 1])
            x = self.cam_x - 30 if side < 0 else self.cam_x + SCREEN_W + 30
            y = random.randint(70, 138)
            self.enemies.append(
                DroneEnemy(
                    x,
                    y,
                    self.player.x + 8,
                    self.player.y + 8,
                    1.1 + self.danger_scale() * 0.18
                )
            )
        if random.random() < self.turret_spawn_chance():
            tx = self.player.x + random.randint(160, 320)
            gy = self.terrain.ground_y_at(tx + 8, self.difficulty_value())
            if not self.terrain.is_gap_at(tx + 8, self.difficulty_value()):
                self.enemies.append(TurretEnemy(tx, gy))
    def update_camera(self):
        target = self.player.x - SCREEN_W // 3
        if self.is_survival() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "SURVIVAL ROTATION"):
            target = self.player.x - SCREEN_W // 2
        if target < 0:
            target = 0
        self.cam_x += (target - self.cam_x) * 0.14
    def handle_player_fire(self):
        if (
            pyxel.btn(pyxel.KEY_LEFT)
            or pyxel.btn(pyxel.KEY_RIGHT)
            or pyxel.btn(pyxel.KEY_UP)
            or pyxel.btn(pyxel.KEY_DOWN)
        ):
            bullets, effects = self.player.fire()
            if bullets:
                self.player_bullets.extend(bullets)
                self.effects.extend(effects)
                sfx(0, 0)
        if pyxel.btnp(pyxel.KEY_K):
            rocket = self.player.fire_rocket()
            if rocket:
                self.rockets.append(rocket)
        if pyxel.btnp(pyxel.KEY_J):
            bomb = self.player.fire_bomb()
            if bomb:
                self.bombs.append(bomb)
    def player_hit(self, dmg=1):
        if self.dev_mode:
            return
        if self.player.take_damage(dmg):
            self.add_shake(6, 2)
            self.spawn_explosion(self.player.x + 8, self.player.y + 10, 7, "muzzle")
            sfx(1, 1)
            if self.player.hp <= 0:
                self.respawn_or_game_over()
    def kill_enemy(self, enemy):
        kind = "nuke" if isinstance(enemy, HeavyBombEnemy) else "normal"
        power = 26 if isinstance(enemy, BossEnemy) else 12
        self.spawn_explosion(enemy.x + enemy.w / 2, enemy.y + enemy.h / 2, power, kind)
        self.player.add_score(enemy.score)
        self.add_text(enemy.x, enemy.y - 10, "+" + str(enemy.score), C10)
        sfx(0, 2)
        coin_count = 1
        if isinstance(enemy, BossEnemy):
            coin_count = 12
        elif isinstance(enemy, BomberEnemy):
            coin_count = 5
        elif isinstance(enemy, HeavyBombEnemy):
            coin_count = 3
        elif isinstance(enemy, TurretEnemy):
            coin_count = 2
        for _ in range(coin_count):
            self.coins.append(Coin(enemy.x + enemy.w / 2, enemy.y + enemy.h / 2, 1))
        if isinstance(enemy, BossEnemy):
            self.add_text(enemy.x, enemy.y - 26, "BOSS DOWN", C14)
            if self.is_campaign() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"):
                self.distance = LEVEL_DISTANCE
            return
        if random.random() < 0.22:
            self.powerups.append(PowerUp(enemy.x + enemy.w / 2, enemy.y + enemy.h / 2))
    def bullet_hits_enemy(self, bullet, enemy):
        if rects_overlap(bullet.x - 2, bullet.y - 2, 4, 4, enemy.x, enemy.y, enemy.w, enemy.h):
            dead = enemy.hurt(bullet.dmg)
            bullet.alive = False
            self.spawn_smoke(bullet.x, bullet.y, 2)
            if dead:
                self.kill_enemy(enemy)
            return True
        return False
    def handle_collisions(self):
        px, py, pw, ph = self.player.hurtbox()
        for b in self.player_bullets:
            if not b.alive:
                continue
            for e in self.enemies:
                if e.alive and self.bullet_hits_enemy(b, e):
                    break
        for r in self.rockets:
            if not r.alive:
                continue
            for e in self.enemies:
                if not e.alive:
                    continue
                cx = e.x + e.w / 2
                cy = e.y + e.h / 2
                if dist2(r.x, r.y, cx, cy) < 14 * 14:
                    r.alive = False
                    dead = e.hurt(4)
                    self.spawn_explosion(r.x, r.y, 10, "normal")
                    self.add_shake(5, 2)
                    if dead:
                        self.kill_enemy(e)
                    break
        for b in self.bombs:
            if not b.alive:
                continue
            hit_enemy = False
            for e in self.enemies:
                if not e.alive:
                    continue
                if rects_overlap(b.x - 5, b.y - 5, 10, 10, e.x, e.y, e.w, e.h):
                    b.alive = False
                    self.spawn_explosion(b.x, b.y, b.power, "nuke")
                    self.add_shake(10, 3)
                    dead = e.hurt(5)
                    if dead:
                        self.kill_enemy(e)
                    hit_enemy = True
                    break
            if hit_enemy:
                continue
            gy = self.terrain.ground_y_at(b.x, self.difficulty_value())
            gap = self.terrain.is_gap_at(b.x, self.difficulty_value())
            if not gap and b.y >= gy:
                b.alive = False
                self.spawn_explosion(b.x, gy, b.power, "nuke")
                self.terrain.add_crater(b.x, 24)
                self.add_shake(12, 4)
                for e in self.enemies:
                    if e.alive:
                        cx = e.x + e.w / 2
                        cy = e.y + e.h / 2
                        if dist2(b.x, gy, cx, cy) < 40 * 40:
                            dead = e.hurt(4)
                            if dead:
                                self.kill_enemy(e)
        for b in self.enemy_bullets:
            if b.alive and rects_overlap(px, py, pw, ph, b.x - 2, b.y - 2, 4, 4):
                b.alive = False
                self.player_hit(1)
        for e in self.enemies:
            if e.alive and rects_overlap(px, py, pw, ph, e.x, e.y, e.w, e.h):
                self.player_hit(1)
        for e in self.enemies:
            if not e.alive:
                continue
            if isinstance(e, (MissileEnemy, HeavyBombEnemy)):
                gy = self.terrain.ground_y_at(e.x + e.w / 2, self.difficulty_value())
                gap = self.terrain.is_gap_at(e.x + e.w / 2, self.difficulty_value())
                if not gap and e.y + e.h >= gy:
                    if isinstance(e, HeavyBombEnemy):
                        self.spawn_explosion(e.x + e.w / 2, gy, 22, "nuke")
                        self.terrain.add_crater(e.x + e.w / 2, 26)
                        self.add_shake(18, 5)
                        if dist2(self.player.x + 8, self.player.y + 10, e.x + e.w / 2, gy) < 36 * 36:
                            self.player_hit(2)
                    else:
                        self.spawn_explosion(e.x + e.w / 2, gy, 11, "normal")
                        self.add_shake(6, 2)
                        if dist2(self.player.x + 8, self.player.y + 10, e.x + e.w / 2, gy) < 16 * 16:
                            self.player_hit(1)
                    e.alive = False
        for p in self.powerups:
            if p.alive and rects_overlap(px, py, pw, ph, p.x, p.y, 12, 12):
                p.alive = False
                sfx(0, 3)
                if p.kind == "rapid":
                    self.player.rapid_timer = 420
                    self.add_text(self.player.x, self.player.y - 10, "RAPID FIRE++", C9)
                elif p.kind == "shield":
                    self.player.invuln = 220
                    self.add_text(self.player.x, self.player.y - 10, "SHIELD", C12)
                elif p.kind == "spread":
                    self.player.spread_timer = 300
                    self.add_text(self.player.x, self.player.y - 10, "SPREAD", C15)
                elif p.kind == "rocket":
                    self.player.rockets += 3
                    self.add_text(self.player.x, self.player.y - 10, "+3 ROCKETS", C10)
                elif p.kind == "bomb":
                    self.player.bombs += 2
                    self.add_text(self.player.x, self.player.y - 10, "+2 BOMBS", C8)
                elif p.kind == "overdrive":
                    self.player.overdrive_timer = 360
                    self.player.rapid_timer = max(self.player.rapid_timer, 180)
                    self.add_text(self.player.x, self.player.y - 10, "OVERDRIVE", C14)
                elif p.kind == "super_shield":
                    self.player.super_shield_timer = 360
                    self.player.invuln = 360
                    self.add_text(self.player.x, self.player.y - 10, "SUPER SHIELD", C7)
        self.player_bullets = [b for b in self.player_bullets if b.alive]
        self.enemy_bullets = [b for b in self.enemy_bullets if b.alive]
        self.rockets = [r for r in self.rockets if r.alive]
        self.bombs = [b for b in self.bombs if b.alive]
        self.enemies = [e for e in self.enemies if e.alive]
        self.powerups = [p for p in self.powerups if p.alive]
        self.coins = [c for c in self.coins if c.alive]
    def update_intro_country(self):
        if pyxel.btnp(pyxel.KEY_LEFT):
            self.country_index = (self.country_index - 1) % len(COUNTRIES)
        if pyxel.btnp(pyxel.KEY_RIGHT):
            self.country_index = (self.country_index + 1) % len(COUNTRIES)
        if pyxel.btnp(pyxel.KEY_RETURN):
            self.save_data["selected_country"] = self.country_index
            save_save(self.save_data)
            self.intro_timer = 0
            self.intro_bombs = []
            self.state = "intro_attack"
            play_story_music()
    def update_intro_attack(self):
        self.intro_timer += 1
        if self.intro_timer % 12 == 0 and self.intro_timer < 250:
            self.intro_bombs.append({
                "x": random.randint(32, SCREEN_W - 32),
                "y": -10,
                "vy": random.uniform(2.0, 3.4),
            })
        new_bombs = []
        for b in self.intro_bombs:
            b["y"] += b["vy"]
            if b["y"] >= 118:
                self.spawn_explosion(b["x"], 118, 18, "nuke")
                self.intro_flash = 10
                self.add_shake(8, 3)
                sfx(0, 2)
            else:
                new_bombs.append(b)
        self.intro_bombs = new_bombs
        if self.intro_flash > 0:
            self.intro_flash -= 1
        for e in self.effects:
            e.update()
        for p in self.particles:
            p.update()
        self.effects = [e for e in self.effects if e.alive]
        self.particles = [p for p in self.particles if p.alive]
        if self.intro_timer > 320 and pyxel.btnp(pyxel.KEY_RETURN):
            self.state = "menu"
            stop_story_music()
    def update_menu(self):
        if pyxel.btnp(pyxel.KEY_LEFT):
            self.mode_index = (self.mode_index - 1) % len(self.mode_names)
        if pyxel.btnp(pyxel.KEY_RIGHT):
            self.mode_index = (self.mode_index + 1) % len(self.mode_names)
        if pyxel.btnp(pyxel.KEY_UP):
            self.skin_index = (self.skin_index - 1) % len(Player.SKINS)
        if pyxel.btnp(pyxel.KEY_DOWN):
            self.skin_index = (self.skin_index + 1) % len(Player.SKINS)
        if pyxel.btnp(pyxel.KEY_U):
            if self.skin_index not in self.unlocked_skins:
                cost = SKIN_PRICES[self.skin_index]
                if self.coins_total >= cost:
                    self.coins_total -= cost
                    self.unlocked_skins.append(self.skin_index)
                    self.save_data["coins_total"] = self.coins_total
                    self.save_data["unlocked_skins"] = self.unlocked_skins
                    save_save(self.save_data)
        if self.is_multiplayer():
            if pyxel.btnp(pyxel.KEY_A):
                self.mp_player_count = max(2, self.mp_player_count - 1)
            if pyxel.btnp(pyxel.KEY_D):
                self.mp_player_count = min(8, self.mp_player_count + 1)
            if pyxel.btnp(pyxel.KEY_W) or pyxel.btnp(pyxel.KEY_S):
                self.mp_mode_index = (self.mp_mode_index + 1) % len(self.mp_mode_names)
        if pyxel.btnp(pyxel.KEY_RETURN):
            if self.skin_index in self.unlocked_skins:
                self.player.skin_index = self.skin_index
                self.player.country_index = self.country_index
                if self.is_multiplayer():
                    self.reset_multiplayer_session()
                else:
                    self.reset_run()
    def update_tutorial(self):
        if pyxel.btnp(pyxel.KEY_RETURN):
            self.state = "playing"
            return
        if self.tutorial_step == 0:
            if pyxel.btn(pyxel.KEY_D):
                self.player.x += 1.5
                if self.player.x > 70:
                    self.tutorial_step = 1
        elif self.tutorial_step == 1:
            if pyxel.btnp(pyxel.KEY_W):
                self.tutorial_step = 2
        elif self.tutorial_step == 2:
            if pyxel.btnp(pyxel.KEY_SPACE):
                self.tutorial_step = 3
        elif self.tutorial_step == 3:
            if pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.KEY_LEFT) or pyxel.btn(pyxel.KEY_UP) or pyxel.btn(pyxel.KEY_DOWN):
                bullets, fx = self.player.fire()
                if bullets:
                    self.player_bullets.extend(bullets)
                    self.effects.extend(fx)
                    self.tutorial_step = 4
        elif self.tutorial_step == 4:
            if pyxel.btnp(pyxel.KEY_J):
                bomb = self.player.fire_bomb()
                if bomb:
                    self.bombs.append(bomb)
                    self.tutorial_step = 5
        elif self.tutorial_step == 5:
            if pyxel.btnp(pyxel.KEY_K):
                rocket = self.player.fire_rocket()
                if rocket:
                    self.rockets.append(rocket)
                    self.tutorial_step = 6
        elif self.tutorial_step == 6:
            if pyxel.btnp(pyxel.KEY_SHIFT):
                self.player.weapon_index = (self.player.weapon_index + 1) % len(self.player.WEAPONS)
                self.tutorial_step = 7
        elif self.tutorial_step == 7:
            self.state = "playing"
        for b in self.player_bullets:
            b.update()
        for e in self.effects:
            e.update()
        for r in self.rockets:
            r.update([])
        for b in self.bombs:
            b.update()
        self.player_bullets = [b for b in self.player_bullets if b.alive]
        self.effects = [e for e in self.effects if e.alive]
        self.rockets = [r for r in self.rockets if r.alive]
        self.bombs = [b for b in self.bombs if b.alive]
    def update_story_update(self):
        self.intro_timer += 1
        if self.intro_timer % 18 == 0 and self.intro_timer < 120:
            self.intro_bombs.append({
                "x": random.randint(50, SCREEN_W - 50),
                "y": -10,
                "vy": random.uniform(1.8, 3.0),
            })
        new_bombs = []
        for b in self.intro_bombs:
            b["y"] += b["vy"]
            if b["y"] >= 118:
                self.spawn_explosion(b["x"], 118, 14, "normal")
                self.intro_flash = 5
            else:
                new_bombs.append(b)
        self.intro_bombs = new_bombs
        if self.intro_flash > 0:
            self.intro_flash -= 1
        for e in self.effects:
            e.update()
        for p in self.particles:
            p.update()
        self.effects = [e for e in self.effects if e.alive]
        self.particles = [p for p in self.particles if p.alive]
        if self.intro_timer > 180 and pyxel.btnp(pyxel.KEY_RETURN):
            self.state = "playing"
            stop_story_music()
    def update_pause(self):
        if pyxel.btnp(pyxel.KEY_UP):
            self.pause_index = (self.pause_index - 1) % 2
        if pyxel.btnp(pyxel.KEY_DOWN):
            self.pause_index = (self.pause_index + 1) % 2
        if pyxel.btnp(pyxel.KEY_RETURN):
            if self.pause_index == 0:
                self.state = "playing"
            else:
                self.state = "menu"
                stop_story_music()
    def update_playing(self):
        self.update_dev_buffer()
        self.player.update(self.difficulty_value(), self.dev_mode)
        if self.is_survival() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "SURVIVAL ROTATION"):
            self.survival_frames += 1
        if self.player.y > SCREEN_H + 40:
            self.player_hit(99)
            return
        self.handle_player_fire()
        self.maybe_spawn_enemies()
        for e in self.enemies:
            e.update(self)
            if isinstance(e, (MissileEnemy, HeavyBombEnemy)):
                self.spawn_smoke(e.x + e.w / 2, e.y + e.h, 1)
        for b in self.player_bullets:
            b.update()
        for b in self.enemy_bullets:
            b.update()
        for r in self.rockets:
            r.update(self.enemies)
            self.spawn_smoke(r.x - 2, r.y + 1, 1)
        for b in self.bombs:
            b.update()
            self.spawn_smoke(b.x, b.y, 1)
        for e in self.effects:
            e.update()
        for p in self.particles:
            p.update()
        for t in self.texts:
            t.update()
        for p in self.powerups:
            p.update(self.terrain, self.difficulty_value())
        for c in self.coins:
            c.update(self)
        self.handle_collisions()
        self.effects = [e for e in self.effects if e.alive]
        self.particles = [p for p in self.particles if p.alive]
        self.texts = [t for t in self.texts if t.alive]
        if self.player.vx > 0:
            self.distance += self.player.vx
            self.total_distance += self.player.vx
            if self.is_multiplayer():
                self.mp_segment_progress += self.player.vx
        if self.is_survival() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "SURVIVAL ROTATION"):
            left_limit = self.cam_x + 10
            right_limit = self.cam_x + SCREEN_W - 26
            self.player.x = clamp(self.player.x, left_limit, right_limit)
        self.update_camera()
        self.update_shake()
        if self.is_campaign() and self.distance >= LEVEL_DISTANCE:
            self.next_level()
        elif self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION":
            if self.distance >= 1500:
                self.mp_scores[self.mp_current_player] += self.player.score
                self.level += 1
                self.distance = 0
                self.mp_segment_progress = 0
                self.mp_campaign_segments_left -= 1
                self.mp_current_player = (self.mp_current_player + 1) % self.mp_player_count
                if self.mp_campaign_segments_left <= 0:
                    self.state = "victory"
                    play_story_music()
                else:
                    self.story_progress_index += 1
                    self.state = "story_update"
                    self.intro_timer = 0
                    self.intro_bombs = []
                    play_story_music()
    def update(self):
        if pyxel.btnp(pyxel.KEY_P):
            if self.state == "playing":
                self.state = "paused"
                self.pause_index = 0
                return
        if self.state == "intro_country":
            self.update_intro_country()
            return
        if self.state == "intro_attack":
            self.update_intro_attack()
            return
        if self.state == "menu":
            self.update_menu()
            return
        if self.state == "tutorial":
            self.update_tutorial()
            return
        if self.state == "story_update":
            self.update_story_update()
            return
        if self.state == "paused":
            self.update_pause()
            return
        if pyxel.btnp(pyxel.KEY_ESCAPE):
            if self.state == "playing":
                self.state = "menu"
                return
        if self.state == "victory":
            if pyxel.btnp(pyxel.KEY_RETURN):
                self.save_data["best_survival"] = self.best_survival
                self.save_data["coins_total"] = self.coins_total
                self.save_data["unlocked_skins"] = self.unlocked_skins
                self.save_data["selected_country"] = self.country_index
                self.save_data["extra_hp_bonus"] = self.extra_hp_bonus
                save_save(self.save_data)
                self.state = "menu"
                stop_story_music()
            return
        if self.state == "game_over":
            if pyxel.btnp(pyxel.KEY_RETURN):
                self.save_data["best_survival"] = self.best_survival
                self.save_data["coins_total"] = self.coins_total
                self.save_data["unlocked_skins"] = self.unlocked_skins
                self.save_data["selected_country"] = self.country_index
                self.save_data["extra_hp_bonus"] = self.extra_hp_bonus
                save_save(self.save_data)
                self.state = "menu"
                stop_story_music()
            return
        self.update_playing()
    def draw_background(self):
        pyxel.cls(C1)
        pyxel.circ(258, 34, 18, C7)
        pyxel.circ(264, 30, 14, C6)
        for s in self.stars:
            sx = int((s["x"] - self.cam_x * s["speed"]) % (SCREEN_W + 40)) - 20
            pyxel.pset(sx, s["y"], s["color"])
        for i in range(-2, 18):
            wx = i * 70
            sx = int(wx - self.cam_x * 0.18)
            h = 30 + (i * 13) % 22
            pyxel.tri(sx, 140, sx + 35, 140 - h, sx + 70, 140, C2)
        for i in range(-3, 24):
            wx = i * 40
            sx = int(wx - self.cam_x * 0.32)
            h = 18 + ((i * 9) % 26)
            pyxel.rect(sx, GROUND_Y - h - 26, 24, h, C2)
            pyxel.rect(sx + 4, GROUND_Y - h - 20, 3, 4, C6)
            pyxel.rect(sx + 14, GROUND_Y - h - 14, 3, 4, C6)
        for i in range(-3, 35):
            wx = i * 24
            sx = int(wx - self.cam_x * 0.52)
            h = 10 + ((i * 7) % 20)
            pyxel.rect(sx, GROUND_Y - h - 8, 16, h, C5)
    def draw_bunker_style(self, x, y, country_key):
        sx = int(x - self.cam_x)
        pyxel.rect(sx, y - 34, 38, 34, C4)
        pyxel.rect(sx + 3, y - 29, 32, 6, C6)
        pyxel.rect(sx + 13, y - 16, 10, 16, C0)
        pyxel.line(sx + 4, y - 34, sx + 31, y - 34, C7)
        if country_key in ("switzerland", "japan", "poland", "austria"):
            pyxel.rect(sx + 5, y - 24, 28, 3, C8 if country_key != "japan" else C7)
        elif country_key in ("ukraine", "romania", "france"):
            draw_flag(country_key, sx + 10, y - 30, 14, 8, pyxel.frame_count * 0.15)
        elif country_key in ("usa", "germany", "belgium", "russia"):
            pyxel.rect(sx + 6, y - 24, 26, 3, C10 if country_key != "russia" else C12)
            pyxel.pset(sx + 11, y - 21, C7)
            pyxel.pset(sx + 25, y - 21, C7)
        else:
            pyxel.pset(sx + 8, y - 23, C10)
            pyxel.pset(sx + 28, y - 23, C10)
    def draw_bunkers(self):
        if not (self.is_campaign() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION")):
            return
        bx = self.checkpoint_x - 28
        gy = self.terrain.ground_y_at(bx + 14, self.difficulty_value())
        sx = int(bx - self.cam_x)
        if -50 <= sx <= SCREEN_W + 50:
            self.draw_bunker_style(bx, gy, self.current_country()["key"])
        remaining = max(0, LEVEL_DISTANCE - self.distance) if self.is_campaign() else max(0, 1500 - self.distance)
        if remaining < 360:
            tx = self.player.x + remaining
            gy2 = self.terrain.ground_y_at(tx + 18, self.difficulty_value())
            sx2 = int(tx - self.cam_x)
            if -60 <= sx2 <= SCREEN_W + 60:
                self.draw_bunker_style(tx, gy2, self.current_country()["key"])
                pyxel.text(sx2 + 1, gy2 - 46, "NUKE BUNKER", C7)
    def draw_world(self):
        cam = self.cam_x + self.shake_x
        pyxel.camera(self.shake_x, self.shake_y)
        self.draw_background()
        self.terrain.draw(cam, self.difficulty_value())
        self.draw_bunkers()
        for p in self.powerups:
            p.draw(cam)
        for c in self.coins:
            c.draw(cam)
        for e in self.enemies:
            if hasattr(e, "draw"):
                e.draw(cam)
        for b in self.player_bullets:
            b.draw(cam)
        for b in self.enemy_bullets:
            b.draw(cam)
        for r in self.rockets:
            r.draw(cam)
        for b in self.bombs:
            b.draw(cam)
        for e in self.effects:
            e.draw(cam)
        for p in self.particles:
            p.draw(cam)
        for t in self.texts:
            t.draw(cam)
        self.player.draw(cam)
        pyxel.camera(0, 0)
    def draw_hud(self):
        pyxel.rect(0, 0, SCREEN_W, 24, C0)
        pyxel.text(8, 4, "HP", C7)
        for i in range(self.player.max_hp):
            col = C8 if i < self.player.hp else C5
            pyxel.rect(24 + i * 10, 4, 8, 6, col)
        pyxel.text(8, 14, "$ " + str(self.coins_total), C10)
        pyxel.text(70, 4, "WPN " + self.player.weapon, C7)
        pyxel.text(155, 4, "RKT " + str(self.player.rockets), C10)
        pyxel.text(210, 4, "BMB " + str(self.player.bombs), C8)
        pyxel.text(268, 4, str(self.player.score), C14)
        if self.player.combo > 1:
            pyxel.text(8, 176, "COMBO x" + str(self.player.combo), C10)
        y = 28
        if self.player.rapid_timer > 0:
            pyxel.text(8, y, "RAPID " + str(self.player.rapid_timer // 60) + "s", C9)
            y += 10
        if self.player.spread_timer > 0:
            pyxel.text(8, y, "SPREAD " + str(self.player.spread_timer // 60) + "s", C15)
            y += 10
        if self.player.overdrive_timer > 0:
            pyxel.text(8, y, "OVERDRIVE " + str(self.player.overdrive_timer // 60) + "s", C14)
            y += 10
        if self.player.invuln > 90:
            pyxel.text(8, y, "SHIELD " + str(self.player.invuln // 60) + "s", C12)
        if self.dev_mode:
            pyxel.text(250, 14, "DEV", C14)
        draw_flag(self.current_country()["key"], 292, 10, 16, 10, pyxel.frame_count * 0.2)
        if self.is_survival() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "SURVIVAL ROTATION"):
            big = str(self.survival_frames // 60)
            pyxel.rect(132, 30, 58, 20, C0)
            pyxel.rectb(132, 30, 58, 20, C7)
            pyxel.text(142, 36, "TIME " + big, C14)
        elif self.is_campaign():
            bx = 100
            by = 190
            bw = 180
            fill = int(bw * self.distance / LEVEL_DISTANCE)
            pyxel.rect(bx, by, bw, 6, C5)
            pyxel.rect(bx, by, fill, 6, C13)
            pyxel.rectb(bx, by, bw, 6, C7)
            pyxel.text(100, 180, "LEVEL " + str(self.level) + "/" + str(CAMPAIGN_LEVELS), C7)
            pyxel.text(218, 180, str(int(max(0, LEVEL_DISTANCE - self.distance))) + "m", C7)
        else:
            pyxel.text(188, 180, "MP P" + str(self.mp_current_player + 1), C7)
            pyxel.text(188, 190, self.mp_mode_names[self.mp_mode_index][:16], C10)
    def story_lines_intro(self):
        name = self.current_country()["name"]
        return [
            f"{name} has been struck without warning.",
            "Cities burn. Sirens fail. Command breaks.",
            "Only one path remains.",
            "Reach the final bunker and save the nation by pressing the red button.",
        ]
    def story_lines_progress(self):
        name = self.current_country()["name"]
        lines = [
            f"REPORT: {name} still resists.",
            "Enemy missiles continue to rain from above.",
            "The war isn't finished.",
            "Push forward. Do not break.",
        ]
        idx = min(self.story_progress_index, 3)
        return [lines[idx], "The people still believe in you.", "Advance to the next front."]
    def draw_intro_country(self):
        self.draw_background()
        pyxel.rect(18, 14, 284, 172, C0)
        pyxel.rectb(18, 14, 284, 172, C7)
        pyxel.text(86, 26, "CHOOSE YOUR COUNTRY", C14)
        pyxel.text(56, 38, "YOUR FLAG, YOUR MAP, YOUR PRIDE, YOUR WAR", C7)
        key = self.current_country()["key"]
        draw_country_outline(key, 118, 52, scale=3, color=C7, fill=False)
        draw_flag(key, 150, 92, 20, 12, pyxel.frame_count * 0.2)
        pyxel.text(132, 110, self.current_country()["name"], C10)
        pyxel.text(60, 150, "LEFT/RIGHT = SELECT COUNTRY", C6)
        pyxel.text(60, 160, "ENTER = START", C6)
    def draw_intro_attack(self):
        pyxel.cls(C0)
        key = self.current_country()["key"]
        for i in range(0, SCREEN_W, 18):
            h = 24 + (i * 7) % 36
            pyxel.rect(i, 132 - h, 12, h, C5)
            pyxel.pset(i + 3, 130 - h, C9)
            pyxel.pset(i + 7, 126 - h, C8)
        draw_country_outline(key, 88, 22, scale=4, color=C7, fill=False)
        if self.intro_flash > 0 and (pyxel.frame_count // 2) % 2 == 0:
            draw_country_outline(key, 88, 22, scale=4, color=C8, fill=True)
        draw_flag(key, 146, 40, 28, 18, pyxel.frame_count * 0.15)
        pyxel.line(144, 38, 144, 90, C4)
        for b in self.intro_bombs:
            pyxel.circ(int(b["x"]), int(b["y"]), 4, C8)
            pyxel.circ(int(b["x"]), int(b["y"]), 2, C10)
        for e in self.effects:
            e.draw(0)
        for p in self.particles:
            p.draw(0)
        lines = self.story_lines_intro()
        pyxel.text(38, 144, lines[0], C8)
        if self.intro_timer > 70:
            pyxel.text(38, 154, lines[1], C7)
        if self.intro_timer > 140:
            pyxel.text(38, 164, lines[2], C14)
        if self.intro_timer > 210:
            pyxel.text(38, 174, lines[3], C7)
        if self.intro_timer > 320:
            pyxel.text(100, 186, "PRESS ENTER", C7)
    def draw_menu(self):
        self.draw_background()
        draw_country_outline(
            self.current_country()["key"],
            190,
            34,
            scale=2,
            color=C8 if (pyxel.frame_count // 10) % 2 == 0 else C7,
            fill=False
        )
        for i in range(3):
            bx = 190 + i * 22
            pyxel.circ(bx, 92 + (i % 2) * 8, 8 + (pyxel.frame_count // 8) % 3, C8)
        pyxel.rect(22, 18, 276, 170, C0)
        pyxel.rectb(22, 18, 276, 170, C7)
        pyxel.text(88, 28, "SUPREME LEADER SURVIVAL", C7)
        pyxel.text(40, 58, "COUNTRY", C7)
        draw_flag(self.current_country()["key"], 96, 56, 18, 12, pyxel.frame_count * 0.15)
        pyxel.text(122, 59, self.current_country()["name"], C7)
        pyxel.text(40, 78, "MODE", C7)
        for i, name in enumerate(self.mode_names):
            x = 82 + i * 74
            selected = i == self.mode_index
            if selected:
                pyxel.rect(x - 6, 74, 68, 16, C12)
                pyxel.text(x, 79, name[:9], C0)
            else:
                pyxel.rectb(x - 6, 74, 68, 16, C7)
                pyxel.text(x, 79, name[:9], C7)
        pyxel.text(40, 100, "SKIN", C7)
        skin_name = Player.SKINS[self.skin_index]["name"]
        pyxel.rect(82, 96, 128, 16, Player.SKINS[self.skin_index]["body"])
        pyxel.text(96, 101, skin_name, C0)
        if self.skin_index not in self.unlocked_skins:
            cost = SKIN_PRICES[self.skin_index]
            pyxel.text(216, 101, "LOCK " + str(cost) + "$", C8)
        if self.is_multiplayer():
            pyxel.text(40, 122, "MULTIPLAYER TYPE", C7)
            pyxel.text(160, 122, self.mp_mode_names[self.mp_mode_index][:16], C10)
            pyxel.text(40, 136, "PLAYERS", C7)
            pyxel.text(96, 136, str(self.mp_player_count), C14)
            pyxel.text(126, 136, "A/D CHANGE, W/S MODE", C6)
        pyxel.text(40, 156, "LEFT/RIGHT MODE", C6)
        pyxel.text(40, 166, "UP/DOWN SKIN, U BUY", C6)
        pyxel.text(180, 156, "ENTER START", C6)
        pyxel.text(180, 166, "COINS " + str(self.coins_total), C10)
    def draw_tutorial(self):
        self.draw_world()
        self.draw_hud()
        pyxel.rect(34, 34, 252, 74, C0)
        pyxel.rectb(34, 34, 252, 74, C7)
        pyxel.text(128, 42, "TUTORIAL", C14)
        steps = [
            "MOVE RIGHT WITH D",
            "PRESS W TO JUMP",
            "PRESS SPACE TO DASH",
            "SHOOT WITH ARROWS",
            "PRESS J FOR BOMB",
            "PRESS SHIFT TO CHANGE WEAPON, K TO SHOOT A ROCKET AND P TO PAUSE THE GAME. LETS GO!",
        ]
        pyxel.text(64, 64, steps[min(self.tutorial_step, len(steps) - 1)], C7)
        pyxel.text(86, 86, "PRESS ENTER TO SKIP", C6)
    def draw_story_update(self):
        pyxel.cls(C0)
        key = self.current_country()["key"]
        draw_country_outline(key, 92, 22, scale=4, color=C7, fill=False)
        draw_flag(key, 146, 44, 26, 16, pyxel.frame_count * 0.15)
        for i in range(0, SCREEN_W, 20):
            h = 18 + (i * 5) % 30
            pyxel.rect(i, 132 - h, 12, h, C5)
        for b in self.intro_bombs:
            pyxel.circ(int(b["x"]), int(b["y"]), 3, C8)
        for e in self.effects:
            e.draw(0)
        for p in self.particles:
            p.draw(0)
        lines = self.story_lines_progress()
        pyxel.text(40, 142, lines[0], C14)
        pyxel.text(40, 154, lines[1], C7)
        pyxel.text(40, 166, lines[2], C7)
        if self.intro_timer > 180:
            pyxel.text(104, 186, "PRESS ENTER", C7)
    def draw_paused(self):
        self.draw_world()
        self.draw_hud()
        pyxel.rect(92, 60, 136, 62, C0)
        pyxel.rectb(92, 60, 136, 62, C7)
        pyxel.text(144, 72, "PAUSED", C14)
        opts = ["RESUME", "LEAVE"]
        for i, opt in enumerate(opts):
            y = 90 + i * 12
            if i == self.pause_index:
                pyxel.rect(114, y - 2, 90, 10, C12)
                pyxel.text(146, y, opt, C0)
            else:
                pyxel.text(146, y, opt, C7)
    def draw_victory(self):
        pyxel.cls(C0)
        key = self.current_country()["key"]
        r = 18 + (pyxel.frame_count // 4) % 24
        pyxel.circb(160, 92, r, C14)
        pyxel.circb(160, 92, r + 8, C7)
        draw_country_outline(key, 110, 42, scale=4, color=C10, fill=False)
        draw_flag(key, 146, 78, 28, 18, pyxel.frame_count * 0.25)
        pyxel.text(120, 126, "VICTORY", C14)
        pyxel.text(54, 140, "YOU SAVED " + self.current_country()["name"], C7)
        pyxel.text(98, 154, "FINAL SCORE " + str(self.player.score), C10)
        pyxel.text(78, 170, "PRESS ENTER FOR MENU", C7)
    def draw_game_over(self):
        pyxel.cls(C0)
        pyxel.text(120, 64, "GAME OVER", C8)
        draw_flag(self.current_country()["key"], 146, 78, 26, 16, pyxel.frame_count * 0.1)
        pyxel.text(102, 104, "SURVIVED " + str(self.survival_frames // 60) + "s", C7)
        pyxel.text(104, 118, "BEST " + str(self.best_survival) + "s", C10)
        pyxel.text(102, 132, "SCORE " + str(self.player.score), C14)
        pyxel.text(98, 152, "PRESS ENTER FOR MENU", C7)
    def draw(self):
        if self.state == "intro_country":
            self.draw_intro_country()
            return
        if self.state == "intro_attack":
            self.draw_intro_attack()
            return
        if self.state == "menu":
            self.draw_menu()
            return
        if self.state == "tutorial":
            self.draw_tutorial()
            return
        if self.state == "story_update":
            self.draw_story_update()
            return
        if self.state == "paused":
            self.draw_paused()
            return
        if self.state == "victory":
            self.draw_victory()
            return
        if self.state == "game_over":
            self.draw_game_over()
            return
        self.draw_world()
        self.draw_hud()
COUNTRY_CITIES = {
    "usa": ["New York", "Washington", "Chicago", "Los Angeles"],
    "mexico": ["Mexico City", "Guadalajara", "Monterrey"],
    "china": ["Beijing", "Shanghai", "Shenzhen"],
    "germany": ["Berlin", "Hamburg", "Munich"],
    "serbia": ["Belgrad", "Novi Sad", "Nis"],
    "iran": ["Teheran", "Tabriz", "Isfahan"],
    "israel": ["Jerusalem", "Tel Aviv", "Haifa"],
    "antarctica": ["McMurdo", "Palmer", "South Pole"],
    "india": ["Delhi", "Mumbai", "Kolkata"],
    "italy": ["Rome", "Milan", "Naples"],
    "russia": ["Moscow", "Saint Petersburg", "Volgograd"],
    "poland": ["Warsaw", "Krakow", "Gdansk"],
    "france": ["Paris", "Lyon", "Marseille"],
    "nigeria": ["Lagos", "Abuja", "Kano"],
    "switzerland": ["Zurich", "Bern", "Geneva"],
    "canada": ["Ottawa", "Toronto", "Montreal"],
    "ukraine": ["Kyiv", "Lviv", "Odesa"],
    "turkiye": ["Ankara", "Istanbul", "Izmir"],
    "south_korea": ["Seoul", "Busan", "Incheon"],
    "north_korea": ["Pyongyang", "Sinuiju", "Wonsan"],
    "saudi": ["Riyadh", "Jeddah", "Mecca"],
    "belgium": ["Brussels", "Antwerp", "Ghent"],
    "austria": ["Vienna", "Graz", "Salzburg"],
    "romania": ["Bucharest", "Cluj", "Timisoara"],
    "japan": ["Tokyo", "Osaka", "Kyoto"],
    "uk": ["London", "Manchester", "Birmingham"],
    "spain": ["Madrid", "Barcelona", "Valencia"],
    "brazil": ["Brasilia", "Rio", "Sao Paulo"],
    "australia": ["Canberra", "Sydney", "Melbourne"],
    "south_africa": ["Pretoria", "Cape Town", "Johannesburg"],
}
COUNTRY_MOTTOS = {
    "usa": "Make America Great again!",
    "mexico": "La bandera no cae.",
    "china": "报效国家",
    "germany": "Vorrrrwärrrts!",
    "serbia": "Hold the homeland.",
    "iran": "Defend the last road.",
    "israel": "Stand and survive.",
    "antarctica": "Even ice resists.",
    "india": "Carry the nation forward.",
    "italy": "ogni buco è trincea",
    "russia": "☭Все животные равны, но некоторые животные равнее других.☭",
    "poland": "Still standing.",
    "france": "Allez les bleus!",
    "nigeria": "Resist and rise.",
    "switzerland": "Stand strong in the mountains.",
    "canada": "Guard the north.",
    "ukraine": "Hold every meter.",
    "turkiye": "Etkin, Caydırıcı, Saygın",
    "south_korea": "Push through the storm.",
    "north_korea": "No step back.",
    "saudi": "Protect the homeland.",
    "belgium": "Hold the center.",
    "austria": "Through the passes.",
    "romania": "Protect the frontier.",
    "japan": "Kamikaze",
    "uk": "Hold the kingdom.",
    "spain": "Push on together.",
    "brazil": "raço Forte, Mão Amiga",
    "australia": "Outlast the fire.",
    "south_africa": "Stand for the nation.",
}
def _patched_story_lines_intro(self):
    key = self.current_country()["key"]
    name = self.current_country()["name"]
    cities = COUNTRY_CITIES.get(key, ["Capital", "Harbor", "Frontier"])
    motto = COUNTRY_MOTTOS.get(key, "Carry the flag forward.")
    return [
        f"{name} is under attack.",
        f"{cities[0]} burns. {cities[min(1, len(cities)-1)]} is under missile fire.",
        f"{cities[min(2, len(cities)-1)]} has gone dark. Command is collapsing.",
        motto,
        "Reach the final bunker. Save what remains."
    ]
def _patched_story_lines_progress(self):
    key = self.current_country()["key"]
    name = self.current_country()["name"]
    cities = COUNTRY_CITIES.get(key, ["Capital", "Harbor", "Frontier"])
    motto = COUNTRY_MOTTOS.get(key, "Push forward.")
    phase = self.story_progress_index % 4
    if phase == 0:
        return [
            f"FIELD UPDATE: {name} still resists.",
            f"{cities[0]} reports heavy damage, but the flag still stands.",
            motto,
        ]
    elif phase == 1:
        return [
            f"FRONTLINE REPORT: enemy pressure rising near {cities[min(1, len(cities)-1)]}.",
            "The bunker route remains open for now.",
            "Advance immediately.",
        ]
    elif phase == 2:
        return [
            f"CIVIL DEFENSE MESSAGE: {cities[min(2, len(cities)-1)]} is evacuating.",
            "The nation watches your progress.",
            motto,
        ]
    else:
        return [
            f"COMMAND REPORT: {name} can still be saved.",
            "Missile rain continues. The final road is close.",
            "Do not stop now.",
        ]
Game.story_lines_intro = _patched_story_lines_intro
Game.story_lines_progress = _patched_story_lines_progress
def _patched_update_pause(self):
    if pyxel.btnp(pyxel.KEY_UP):
        self.pause_index = (self.pause_index - 1) % 2
    if pyxel.btnp(pyxel.KEY_DOWN):
        self.pause_index = (self.pause_index + 1) % 2
    if pyxel.btnp(pyxel.KEY_P):
        self.state = "playing"
        return
    if pyxel.btnp(pyxel.KEY_RETURN):
        if self.pause_index == 0:
            self.state = "playing"
        else:
            self.state = "menu"
            stop_story_music()
Game.update_pause = _patched_update_pause
_old_player_draw = Player.draw
def _patched_player_draw(self, cam_x):
    sx = int(self.x - cam_x)
    sy = int(self.y)
    if self.hurt_flash > 0 and (pyxel.frame_count // 2) % 2 == 0:
        return
    body = self.skin["body"]
    cape = self.skin["cape"]
    visor = self.skin["visor"]
    dark_body = max(1, body - 1)
    bright_body = min(15, body + 1)
    step = 0
    if self.on_ground and abs(self.vx) > 0.1:
        step = 1 if (self.walk_frame // 5) % 2 == 0 else -1
    if self.invuln > 0 and (pyxel.frame_count // 4) % 2 == 0:
        col = C7 if self.super_shield_timer > 0 else C12
        pyxel.circb(sx + 8, sy + 11, 13, col)
        pyxel.circb(sx + 8, sy + 11, 11, C6)
    if self.dash_timer > 0:
        for i in range(1, 6):
            ox = sx - self.facing * i * 6
            trail_y = sy + (i % 2)
            pole_x = ox + 20 if self.facing == 1 else ox - 8
            pyxel.line(pole_x, trail_y + 3, pole_x, trail_y + 19, C5)
            draw_flag(
                self.country["key"],
                pole_x + (1 if self.facing == 1 else -14),
                trail_y + 4,
                12,
                7,
                pyxel.frame_count * 0.45 + i * 0.7
            )
            pyxel.rect(ox + 4, trail_y + 7, 8, 10, C6)
    cape_wave = 1 if (pyxel.frame_count // 4) % 2 == 0 else -1
    pyxel.tri(sx + 4, sy + 8, sx - 4, sy + 21 + cape_wave, sx + 5, sy + 20, cape)
    pyxel.tri(sx + 12, sy + 8, sx + 20, sy + 21 - cape_wave, sx + 11, sy + 20, cape)
    pyxel.line(sx + 6, sy + 8, sx + 3, sy + 20, dark_body)
    pyxel.line(sx + 10, sy + 8, sx + 13, sy + 20, dark_body)
    pole_x = sx + 20 if self.facing == 1 else sx - 8
    pyxel.line(pole_x, sy + 1, pole_x, sy + 23, C4)
    if self.facing == 1:
        draw_flag(self.country["key"], pole_x + 1, sy + 3, 15, 9, pyxel.frame_count * 0.28)
    else:
        draw_flag(self.country["key"], pole_x - 16, sy + 3, 15, 9, pyxel.frame_count * 0.28)
    pyxel.rect(sx + 2, sy - 1, 12, 8, C0)
    pyxel.rect(sx + 1, sy + 5, 14, 14, C0)
    pyxel.rect(sx + 4, sy, 8, 5, C11)
    pyxel.rect(sx + 3, sy + 1, 10, 3, C11)
    pyxel.rect(sx + 3, sy - 2, 10, 2, visor)
    pyxel.pset(sx + 7, sy - 3, C14)
    pyxel.pset(sx + 8, sy - 3, C14)
    if self.facing == 1:
        pyxel.rect(sx + 7, sy + 2, 4, 2, visor)
        pyxel.pset(sx + 10, sy + 2, C7)
    else:
        pyxel.rect(sx + 5, sy + 2, 4, 2, visor)
        pyxel.pset(sx + 5, sy + 2, C7)
    pyxel.rect(sx + 1, sy + 6, 4, 4, bright_body)
    pyxel.rect(sx + 11, sy + 6, 4, 4, bright_body)
    pyxel.line(sx + 1, sy + 6, sx + 4, sy + 5, C7)
    pyxel.line(sx + 11, sy + 6, sx + 14, sy + 5, C7)
    pyxel.rect(sx + 4, sy + 6, 8, 11, body)
    pyxel.rect(sx + 5, sy + 7, 6, 9, bright_body)
    pyxel.line(sx + 8, sy + 6, sx + 8, sy + 16, dark_body)
    pyxel.line(sx + 4, sy + 7, sx + 11, sy + 15, C8)
    pyxel.line(sx + 5, sy + 7, sx + 12, sy + 14, C14)
    pyxel.pset(sx + 10, sy + 10, C14)
    pyxel.pset(sx + 10, sy + 11, C10)
    pyxel.pset(sx + 6, sy + 11, C7)
    pyxel.rect(sx + 4, sy + 16, 8, 2, C4)
    pyxel.pset(sx + 8, sy + 16, C14)
    if self.shoot_anim > 0:
        if self.facing == 1:
            pyxel.line(sx + 12, sy + 9, sx + 16, sy + 9, bright_body)
            pyxel.line(sx + 4, sy + 9, sx + 1, sy + 12, dark_body)
        else:
            pyxel.line(sx + 4, sy + 9, sx, sy + 9, bright_body)
            pyxel.line(sx + 12, sy + 9, sx + 15, sy + 12, dark_body)
    else:
        pyxel.line(sx + 4, sy + 9, sx + 1, sy + 13, dark_body)
        pyxel.line(sx + 12, sy + 9, sx + 15, sy + 13, dark_body)
    pyxel.rect(sx + 5, sy + 18, 3, 4, dark_body)
    pyxel.rect(sx + 9, sy + 18, 3, 4, dark_body)
    pyxel.line(sx + 6, sy + 22, sx + 5 + step, sy + 25, C5)
    pyxel.line(sx + 10, sy + 22, sx + 11 - step, sy + 25, C5)
    pyxel.rect(sx + 3 + step, sy + 24, 4, 2, C0)
    pyxel.rect(sx + 9 - step, sy + 24, 4, 2, C0)
    if self.facing == 1:
        pyxel.rect(sx + 14, sy + 8, 4, 2, C5)
        pyxel.rect(sx + 17, sy + 8, 3, 1, C7)
    else:
        pyxel.rect(sx - 2, sy + 8, 4, 2, C5)
        pyxel.rect(sx - 3, sy + 8, 3, 1, C7)
    if self.shoot_anim > 0:
        mx = sx + 20 if self.facing == 1 else sx - 3
        my = sy + 9
        pyxel.circ(mx, my, 3, C9)
        pyxel.circ(mx, my, 2, C10)
        pyxel.pset(mx, my, C7)
Player.draw = _patched_player_draw
_old_draw_hud = Game.draw_hud
def _patched_draw_hud(self):
    _old_draw_hud(self)
    if self.is_multiplayer():
        pyxel.rect(212, 28, 102, 10 + self.mp_player_count * 8, C0)
        pyxel.rectb(212, 28, 102, 10 + self.mp_player_count * 8, C7)
        pyxel.text(218, 32, "MULTIPLAYER", C14)
        for i in range(self.mp_player_count):
            y = 42 + i * 8
            col = C10 if i == self.mp_current_player else C7
            score = self.mp_scores[i] if i < len(self.mp_scores) else 0
            pyxel.text(218, y, f"P{i+1}: {score}", col)
Game.draw_hud = _patched_draw_hud
def _patched_draw_intro_attack(self):
    pyxel.cls(C0)
    key = self.current_country()["key"]
    cities = COUNTRY_CITIES.get(key, ["Capital", "Harbor", "Frontier"])
    for i in range(0, SCREEN_W, 14):
        h = 18 + (i * 9) % 42
        pyxel.rect(i, 138 - h, 9, h, C5)
        pyxel.pset(i + 2, 136 - h, C8)
        pyxel.pset(i + 5, 130 - h, C9)
    draw_country_outline(key, 88, 18, scale=4, color=C7, fill=False)
    if self.intro_flash > 0 and (pyxel.frame_count // 2) % 2 == 0:
        draw_country_outline(key, 88, 18, scale=4, color=C8, fill=True)
    draw_flag(key, 146, 38, 28, 18, pyxel.frame_count * 0.15)
    pyxel.line(144, 36, 144, 90, C4)
    for b in self.intro_bombs:
        pyxel.circ(int(b["x"]), int(b["y"]), 4, C8)
        pyxel.circ(int(b["x"]), int(b["y"]), 2, C10)
    for e in self.effects:
        e.draw(0)
    for p in self.particles:
        p.draw(0)
    pyxel.text(26, 144, f"{cities[0]} is burning.", C8)
    if self.intro_timer > 60:
        pyxel.text(26, 154, f"{cities[min(1, len(cities)-1)]} is under missile fire.", C7)
    if self.intro_timer > 130:
        pyxel.text(26, 164, f"{cities[min(2, len(cities)-1)]} has gone dark.", C7)
    if self.intro_timer > 200:
        pyxel.text(26, 174, COUNTRY_MOTTOS.get(key, "Carry the flag forward."), C14)
    if self.intro_timer > 320:
        pyxel.text(100, 186, "PRESS ENTER", C7)
Game.draw_intro_attack = _patched_draw_intro_attack
def _patched_draw_story_update(self):
    pyxel.cls(C0)
    key = self.current_country()["key"]
    draw_country_outline(key, 92, 22, scale=4, color=C7, fill=False)
    draw_flag(key, 146, 44, 26, 16, pyxel.frame_count * 0.15)
    for i in range(0, SCREEN_W, 20):
        h = 18 + (i * 5) % 30
        pyxel.rect(i, 132 - h, 12, h, C5)
    for b in self.intro_bombs:
        pyxel.circ(int(b["x"]), int(b["y"]), 3, C8)
    for e in self.effects:
        e.draw(0)
    for p in self.particles:
        p.draw(0)
    lines = self.story_lines_progress()
    pyxel.text(28, 140, lines[0], C14)
    pyxel.text(28, 154, lines[1], C7)
    pyxel.text(28, 168, lines[2], C7)
    if self.intro_timer > 180:
        pyxel.text(104, 186, "PRESS ENTER", C7)
Game.draw_story_update = _patched_draw_story_update
def _patched_draw_victory(self):
    pyxel.cls(C0)
    key = self.current_country()["key"]
    r = 18 + (pyxel.frame_count // 4) % 24
    pyxel.circb(160, 92, r, C14)
    pyxel.circb(160, 92, r + 8, C7)
    pyxel.circb(160, 92, r + 16, C10)
    draw_country_outline(key, 108, 38, scale=4, color=C10, fill=False)
    draw_flag(key, 146, 76, 28, 18, pyxel.frame_count * 0.25)
    pyxel.text(118, 126, "VICTORY", C14)
    pyxel.text(44, 140, "THE FLAG STILL FLIES OVER " + self.current_country()["name"], C7)
    pyxel.text(98, 154, "FINAL SCORE " + str(self.player.score), C10)
    pyxel.text(78, 170, "PRESS ENTER FOR MENU", C7)
Game.draw_victory = _patched_draw_victory
_old_game_reset_run_more = Game.reset_run
_old_game_update_playing_more = Game.update_playing
_old_game_player_hit_more = Game.player_hit
_old_game_kill_enemy_more = Game.kill_enemy
_old_game_draw_world_more = Game.draw_world
_old_game_draw_menu_more = Game.draw_menu
_old_game_draw_hud_more = Game.draw_hud
def _difficulty_label(value):
    if value < 3:
        return "CALM"
    if value < 6:
        return "HOT"
    if value < 9:
        return "CHAOS"
    return "APOCALYPSE"
def _smart_powerup_kind(game):
    """Bias drops toward what the player currently needs."""
    p = game.player
    weighted = []
    def add(kind, weight):
        weighted.extend([kind] * max(0, int(weight)))
    add("shield", 8 if p.hp <= max(1, p.max_hp // 2) else 2)
    add("rocket", 7 if p.rockets <= 1 else 2)
    add("bomb", 7 if p.bombs <= 1 else 2)
    add("rapid", 4 if p.rapid_timer <= 0 else 1)
    add("spread", 4 if p.spread_timer <= 0 else 1)
    add("overdrive", 2)
    add("super_shield", 1 if p.hp <= 2 else 0)
    return random.choice(weighted or PowerUp.TYPES)
def _patched_reset_run_more(self):
    _old_game_reset_run_more(self)
    self.hitstop_timer = 0
    self.near_miss_cooldown = 0
    self.combo_reward_next = 8
Game.reset_run = _patched_reset_run_more
def _patched_player_hit_more(self, dmg=1):
    before_hp = self.player.hp
    _old_game_player_hit_more(self, dmg)
    if not self.dev_mode and self.player.hp < before_hp:
        self.hitstop_timer = max(getattr(self, "hitstop_timer", 0), 7)
        if self.player.hp <= 2:
            self.add_text(self.player.x, self.player.y - 18, "CRITICAL", C8)
Game.player_hit = _patched_player_hit_more
def _patched_kill_enemy_more(self, enemy):
    old_combo = self.player.combo
    _old_game_kill_enemy_more(self, enemy)
    if isinstance(enemy, BossEnemy):
        self.hitstop_timer = 18
    elif isinstance(enemy, (BomberEnemy, HeavyBombEnemy, TurretEnemy)):
        self.hitstop_timer = max(getattr(self, "hitstop_timer", 0), 5)
    if old_combo >= 0 and random.random() < 0.08:
        self.powerups.append(PowerUp(enemy.x + enemy.w / 2, enemy.y + enemy.h / 2, _smart_powerup_kind(self)))
    next_reward = getattr(self, "combo_reward_next", 8)
    if self.player.combo >= next_reward:
        self.player.rockets += 1
        if self.player.hp < self.player.max_hp and next_reward % 16 == 0:
            self.player.hp += 1
            self.add_text(self.player.x, self.player.y - 24, "COMBO HEAL", C3)
        else:
            self.add_text(self.player.x, self.player.y - 24, "COMBO ROCKET", C10)
        self.combo_reward_next = next_reward + 8
Game.kill_enemy = _patched_kill_enemy_more
def _patched_update_playing_more(self):
    if getattr(self, "hitstop_timer", 0) > 0:
        self.hitstop_timer -= 1
        self.update_shake()
        for e in self.effects:
            e.update()
        for p in self.particles:
            p.update()
        for t in self.texts:
            t.update()
        self.effects = [e for e in self.effects if e.alive]
        self.particles = [p for p in self.particles if p.alive]
        self.texts = [t for t in self.texts if t.alive]
        return
    if getattr(self, "near_miss_cooldown", 0) > 0:
        self.near_miss_cooldown -= 1
    _old_game_update_playing_more(self)
    if getattr(self, "near_miss_cooldown", 0) <= 0 and self.player.invuln <= 0:
        px, py, pw, ph = self.player.hurtbox()
        cx = px + pw / 2
        cy = py + ph / 2
        for b in self.enemy_bullets:
            if b.alive and 18 * 18 < dist2(cx, cy, b.x, b.y) < 30 * 30:
                self.player.score += 3
                self.add_text(cx, cy - 16, "NEAR MISS +3", C14)
                self.near_miss_cooldown = 45
                break
Game.update_playing = _patched_update_playing_more
def _patched_update_menu_more(self):
    if pyxel.btnp(pyxel.KEY_Q):
        self.country_index = (self.country_index - 1) % len(COUNTRIES)
        self.player.country_index = self.country_index
        self.save_data["selected_country"] = self.country_index
        save_save(self.save_data)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_E):
        self.country_index = (self.country_index + 1) % len(COUNTRIES)
        self.player.country_index = self.country_index
        self.save_data["selected_country"] = self.country_index
        save_save(self.save_data)
        sfx(1, 4)
    Game.__dict__["_original_update_menu_before_more"](self)
if not hasattr(Game, "_original_update_menu_before_more"):
    Game._original_update_menu_before_more = Game.update_menu
Game.update_menu = _patched_update_menu_more
def _patched_update_pause_more(self):
    opts = 3
    if pyxel.btnp(pyxel.KEY_UP):
        self.pause_index = (self.pause_index - 1) % opts
    if pyxel.btnp(pyxel.KEY_DOWN):
        self.pause_index = (self.pause_index + 1) % opts
    if pyxel.btnp(pyxel.KEY_P):
        self.state = "playing"
        return
    if pyxel.btnp(pyxel.KEY_RETURN):
        if self.pause_index == 0:
            self.state = "playing"
        elif self.pause_index == 1:
            self.reset_run()
        else:
            self.state = "menu"
            stop_story_music()
Game.update_pause = _patched_update_pause_more
def _patched_draw_paused_more(self):
    self.draw_world()
    self.draw_hud()
    pyxel.rect(78, 52, 164, 88, C0)
    pyxel.rectb(78, 52, 164, 88, C7)
    pyxel.text(144, 64, "PAUSED", C14)
    opts = ["RESUME", "RESTART RUN", "LEAVE TO MENU"]
    for i, opt in enumerate(opts):
        y = 84 + i * 14
        if i == self.pause_index:
            pyxel.rect(100, y - 2, 120, 10, C12)
            pyxel.text(118, y, opt, C0)
        else:
            pyxel.text(118, y, opt, C7)
    pyxel.text(96, 126, "P RESUME   ENTER SELECT", C6)
Game.draw_paused = _patched_draw_paused_more
def _patched_draw_menu_more(self):
    _old_game_draw_menu_more(self)
    pyxel.text(40, 46, "Q/E COUNTRY", C6)
    pyxel.text(222, 59, str(self.country_index + 1) + "/" + str(len(COUNTRIES)), C6)
    if self.skin_index in self.unlocked_skins:
        pyxel.text(216, 101, "OWNED", C3)
Game.draw_menu = _patched_draw_menu_more
def _patched_draw_hud_more(self):
    _old_game_draw_hud_more(self)
    if self.player.hp <= 2 and (pyxel.frame_count // 8) % 2 == 0:
        pyxel.rectb(2, 26, SCREEN_W - 4, SCREEN_H - 30, C8)
        pyxel.text(136, 28, "LOW HP", C8)
    label = _difficulty_label(self.difficulty_value())
    pyxel.text(250, 24, "THREAT " + label[:4], C8 if label in ("CHAOS", "APOCALYPSE") else C6)
    if self.player.special_cd > 0:
        pyxel.rect(154, 14, 50, 5, C5)
        fill = int(50 * (1 - self.player.special_cd / 18))
        pyxel.rect(154, 14, fill, 5, C10)
        pyxel.rectb(154, 14, 50, 5, C7)
Game.draw_hud = _patched_draw_hud_more
def _patched_draw_world_more(self):
    _old_game_draw_world_more(self)
    for e in self.enemies:
        if not e.alive:
            continue
        sx = int(e.x + e.w / 2 - self.cam_x)
        sy = int(e.y + e.h / 2)
        dangerous = isinstance(e, (MissileEnemy, HeavyBombEnemy, BomberEnemy, BossEnemy))
        if not dangerous:
            continue
        if sy < 18:
            x = clamp(sx, 10, SCREEN_W - 10)
            pyxel.tri(x, 18, x - 5, 8, x + 5, 8, C8)
        elif sx < 0:
            pyxel.tri(6, clamp(sy, 30, SCREEN_H - 20), 16, clamp(sy, 30, SCREEN_H - 20) - 5, 16, clamp(sy, 30, SCREEN_H - 20) + 5, C8)
        elif sx > SCREEN_W:
            pyxel.tri(SCREEN_W - 6, clamp(sy, 30, SCREEN_H - 20), SCREEN_W - 16, clamp(sy, 30, SCREEN_H - 20) - 5, SCREEN_W - 16, clamp(sy, 30, SCREEN_H - 20) + 5, C8)
Game.draw_world = _patched_draw_world_more
class SupplyDrop:
    def __init__(self, x, y=-50, kind="field"):
        self.x = x
        self.y = y
        self.vx = random.uniform(-0.25, 0.25)
        self.vy = random.uniform(0.55, 0.85)
        self.swing = random.random() * math.tau
        self.kind = kind
        self.hp = 3
        self.opened = False
        self.alive = True
        self.landed = False
        self.timer = 0
        self.w = 18
        self.h = 14
    def update(self, game):
        self.timer += 1
        self.swing += 0.06
        if not self.landed:
            self.vy = min(self.vy + 0.018, 1.55)
            self.x += self.vx + math.sin(self.swing) * 0.16
            self.y += self.vy
            gy = game.terrain.ground_y_at(self.x, game.difficulty_value())
            if not game.terrain.is_gap_at(self.x, game.difficulty_value()) and self.y + self.h >= gy:
                self.y = gy - self.h
                self.landed = True
                self.vy = 0
                game.spawn_smoke(self.x, gy - 2, 6)
                game.add_text(self.x - 8, self.y - 20, "SUPPLY READY", C10)
        else:
            if self.x < game.cam_x - 120:
                self.alive = False
    def open(self, game, violent=False):
        if self.opened or not self.alive:
            return
        self.opened = True
        self.alive = False
        cx = self.x + self.w / 2
        cy = self.y + self.h / 2
        game.spawn_explosion(cx, cy, 9 if violent else 6, "normal")
        game.add_shake(5, 2)
        sfx(0, 3)
        coin_count = 8 if self.kind == "field" else 12
        for _ in range(coin_count):
            game.coins.append(Coin(cx + random.randint(-5, 5), cy + random.randint(-4, 4), random.choice([1, 1, 1, 2])))
        item = _smart_powerup_kind(game)
        game.powerups.append(PowerUp(cx - 6, cy - 12, item))
        if random.random() < 0.45:
            game.player.rockets += 1
            game.add_text(cx - 12, cy - 24, "+ROCKET", C10)
        if random.random() < 0.22 and game.player.hp < game.player.max_hp:
            game.player.hp += 1
            game.add_text(cx - 4, cy - 34, "+HP", C3)
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        if sx < -60 or sx > SCREEN_W + 60:
            return
        bob = int(math.sin(self.swing) * 2)
        crate_x = sx
        crate_y = sy
        canopy_y = sy - 30 + bob
        canopy_x = sx + 9 + int(math.sin(self.swing * 0.7) * 4)
        if not self.landed:
            pyxel.circ(canopy_x - 12, canopy_y + 8, 11, C8)
            pyxel.circ(canopy_x, canopy_y + 4, 15, C8)
            pyxel.circ(canopy_x + 12, canopy_y + 8, 11, C8)
            pyxel.circ(canopy_x - 8, canopy_y + 10, 7, C10)
            pyxel.circ(canopy_x + 8, canopy_y + 10, 7, C10)
            pyxel.rect(canopy_x - 22, canopy_y + 9, 44, 4, C8)
            pyxel.line(canopy_x - 19, canopy_y + 13, crate_x + 2, crate_y, C7)
            pyxel.line(canopy_x - 6, canopy_y + 16, crate_x + 6, crate_y, C7)
            pyxel.line(canopy_x + 6, canopy_y + 16, crate_x + 12, crate_y, C7)
            pyxel.line(canopy_x + 19, canopy_y + 13, crate_x + 16, crate_y, C7)
            pyxel.pset(canopy_x - 15, canopy_y + 5, C7)
            pyxel.pset(canopy_x, canopy_y + 1, C7)
            pyxel.pset(canopy_x + 15, canopy_y + 5, C7)
        else:
            pyxel.line(crate_x - 8, crate_y + self.h + 2, crate_x + 25, crate_y + self.h + 2, C8)
            pyxel.line(crate_x - 4, crate_y + self.h + 4, crate_x + 19, crate_y + self.h + 4, C10)
        shadow_y = crate_y + self.h + 3
        pyxel.line(crate_x - 3, shadow_y, crate_x + self.w + 3, shadow_y, C5)
        pyxel.rect(crate_x, crate_y, self.w, self.h, C4)
        pyxel.rect(crate_x + 2, crate_y + 2, self.w - 4, self.h - 4, C10)
        pyxel.rectb(crate_x, crate_y, self.w, self.h, C7)
        pyxel.line(crate_x, crate_y, crate_x + self.w - 1, crate_y + self.h - 1, C5)
        pyxel.line(crate_x + self.w - 1, crate_y, crate_x, crate_y + self.h - 1, C5)
        pyxel.text(crate_x + 4, crate_y + 4, "+", C7)
        if self.landed and (pyxel.frame_count // 10) % 2 == 0:
            pyxel.text(crate_x - 8, crate_y - 10, "OPEN", C7)
FIELD_ORDER_POOL = [
    {
        "code": "GHOST LINE",
        "brief": "ADVANCE WITHOUT TAKING DAMAGE",
        "kind": "nohit_distance",
        "goal": 420,
        "limit": 52 * FPS,
        "color": C12,
    },
    {
        "code": "SCRAP HARVEST",
        "brief": "COLLECT COINS FROM THE WARZONE",
        "kind": "coins",
        "goal": 18,
        "limit": 58 * FPS,
        "color": C10,
    },
    {
        "code": "CHAIN COMMAND",
        "brief": "BUILD A COMBO STREAK",
        "kind": "combo",
        "goal": 10,
        "limit": 50 * FPS,
        "color": C14,
    },
    {
        "code": "SKY CLEANER",
        "brief": "DESTROY HOSTILES",
        "kind": "kills",
        "goal": 9,
        "limit": 55 * FPS,
        "color": C8,
    },
    {
        "code": "EDGEWALKER",
        "brief": "SCORE NEAR-MISS DODGES",
        "kind": "near",
        "goal": 3,
        "limit": 60 * FPS,
        "color": C15,
    },
]
def _ensure_creative_state(game):
    if not hasattr(game, "supply_drops"):
        game.supply_drops = []
    if not hasattr(game, "field_order"):
        game.field_order = None
        game.field_order_progress = 0
        game.field_order_timer = 0
        game.field_order_cooldown = 4 * FPS
        game.field_order_start_x = game.player.x
        game.field_order_start_coins = game.coins_total
        game.field_order_start_kills = 0
        game.field_order_kills = 0
        game.field_order_near = 0
        game.damage_taken_run = 0
        game.field_order_start_damage = 0
    if not hasattr(game, "dev_spawn_pct"):
        game.dev_spawn_pct = 100
    if not hasattr(game, "dev_gravity_pct"):
        game.dev_gravity_pct = int(GRAVITY * 100)
    if not hasattr(game, "dev_move_pct"):
        game.dev_move_pct = int(game.player.speed * 10)
    if not hasattr(game, "dev_jump_pct"):
        game.dev_jump_pct = int(abs(game.player.jump_power) * 10)
    if not hasattr(game, "dev_enemy_cap"):
        game.dev_enemy_cap = 32
    if not hasattr(game, "dev_cursor"):
        game.dev_cursor = 0
        game.dev_panel_open = False
        game.dev_page = 0
def _start_field_order(game, forced=None):
    _ensure_creative_state(game)
    order = dict(forced or random.choice(FIELD_ORDER_POOL))
    d = game.difficulty_value()
    if order["kind"] == "kills":
        order["goal"] += min(6, int(d * 0.6))
    elif order["kind"] == "coins":
        order["goal"] += min(16, int(d * 1.2))
    elif order["kind"] == "combo":
        order["goal"] += min(6, int(d * 0.4))
    game.field_order = order
    game.field_order_progress = 0
    game.field_order_timer = order["limit"]
    game.field_order_start_x = game.player.x
    game.field_order_start_coins = game.coins_total
    game.field_order_start_kills = game.field_order_kills
    game.field_order_start_near = game.field_order_near
    game.field_order_start_damage = game.damage_taken_run
    game.add_text(game.player.x + 30, 42, "FIELD ORDER: " + order["code"], order["color"])
    sfx(1, 6)
def _complete_field_order(game):
    order = game.field_order
    if not order:
        return
    game.player.score += 90 + int(order["goal"] * 3)
    game.coins_total += 10
    game.player.rockets += 1
    game.add_text(game.player.x + 18, 38, "ORDER COMPLETE", C14)
    game.add_text(game.player.x + 18, 50, "+SUPPLY +$10 +RKT", C10)
    game.supply_drops.append(SupplyDrop(game.player.x + 130, -60, "field"))
    game.field_order = None
    game.field_order_cooldown = 7 * FPS
def _fail_field_order(game, reason="FAILED"):
    if game.field_order:
        game.add_text(game.player.x + 18, 42, "ORDER " + reason, C8)
    game.field_order = None
    game.field_order_cooldown = 6 * FPS
def _update_field_order(game):
    _ensure_creative_state(game)
    if game.dev_mode and getattr(game, "dev_panel_open", False):
        return
    if not game.field_order:
        game.field_order_cooldown -= 1
        if game.field_order_cooldown <= 0:
            _start_field_order(game)
        return
    order = game.field_order
    game.field_order_timer -= 1
    kind = order["kind"]
    if kind == "nohit_distance":
        if game.damage_taken_run > game.field_order_start_damage:
            _fail_field_order(game, "BROKEN")
            return
        game.field_order_progress = max(0, int(game.player.x - game.field_order_start_x))
    elif kind == "coins":
        game.field_order_progress = max(0, game.coins_total - game.field_order_start_coins)
    elif kind == "combo":
        game.field_order_progress = max(game.field_order_progress, game.player.combo)
    elif kind == "kills":
        game.field_order_progress = max(0, game.field_order_kills - game.field_order_start_kills)
    elif kind == "near":
        game.field_order_progress = max(0, game.field_order_near - game.field_order_start_near)
    if game.field_order_progress >= order["goal"]:
        _complete_field_order(game)
    elif game.field_order_timer <= 0:
        _fail_field_order(game, "MISSED")
def _dev_rows(game):
    return [
        ("LEVEL", game.level, 1, CAMPAIGN_LEVELS, 1, lambda v: _dev_set_level(game, v)),
        ("DIST", int(game.distance), 0, LEVEL_DISTANCE - 40, 40, lambda v: setattr(game, "distance", v)),
        ("HP", game.player.hp, 1, 30, 1, lambda v: _dev_set_hp(game, v)),
        ("RKT", game.player.rockets, 0, 99, 1, lambda v: setattr(game.player, "rockets", v)),
        ("BMB", game.player.bombs, 0, 99, 1, lambda v: setattr(game.player, "bombs", v)),
        ("COIN", game.coins_total, 0, 9999, 25, lambda v: setattr(game, "coins_total", v)),
        ("SPAWN%", game.dev_spawn_pct, 0, 300, 10, lambda v: setattr(game, "dev_spawn_pct", v)),
        ("GRAV", game.dev_gravity_pct, 0, 90, 2, lambda v: _dev_set_gravity(game, v)),
        ("MOVE", game.dev_move_pct, 5, 50, 1, lambda v: _dev_set_move(game, v)),
        ("JUMP", game.dev_jump_pct, 20, 100, 2, lambda v: _dev_set_jump(game, v)),
        ("CAP", game.dev_enemy_cap, 0, 80, 2, lambda v: setattr(game, "dev_enemy_cap", v)),
    ]
def _dev_set_level(game, v):
    game.level = int(v)
    game.checkpoint_level = int(v)
    game.distance = clamp(game.distance, 0, LEVEL_DISTANCE - 40)
    game.add_text(game.player.x + 12, 34, "LEVEL " + str(game.level), C14)
def _dev_set_hp(game, v):
    game.player.max_hp = max(game.player.max_hp, int(v))
    game.player.hp = int(v)
def _dev_set_gravity(game, v):
    global GRAVITY
    game.dev_gravity_pct = int(v)
    GRAVITY = int(v) / 100.0
def _dev_set_move(game, v):
    game.dev_move_pct = int(v)
    game.player.speed = int(v) / 10.0
    game.player.air_speed = max(0.8, game.player.speed - 0.3)
def _dev_set_jump(game, v):
    game.dev_jump_pct = int(v)
    game.player.jump_power = -int(v) / 10.0
def _apply_dev_delta(game, delta):
    rows = _dev_rows(game)
    game.dev_cursor %= len(rows)
    label, value, lo, hi, step, apply = rows[game.dev_cursor]
    if pyxel.btn(pyxel.KEY_SHIFT):
        step *= 5
    new_value = int(clamp(value + delta * step, lo, hi))
    apply(new_value)
    sfx(1, 4)
def _dev_spawn_boss(game):
    if not any(isinstance(e, BossEnemy) and e.alive for e in game.enemies):
        game.enemies.append(BossEnemy(game.player.x + 180))
        game.add_text(game.player.x + 24, 34, "DEV BOSS", C8)
def _dev_update_panel(game):
    _ensure_creative_state(game)
    rows = _dev_rows(game)
    if pyxel.btnp(pyxel.KEY_UP):
        game.dev_cursor = (game.dev_cursor - 1) % len(rows)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_DOWN):
        game.dev_cursor = (game.dev_cursor + 1) % len(rows)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_LEFT):
        _apply_dev_delta(game, -1)
    if pyxel.btnp(pyxel.KEY_RIGHT):
        _apply_dev_delta(game, 1)
    if pyxel.btnp(pyxel.KEY_SPACE):
        game.dev_panel_open = False
        game.add_text(game.player.x + 10, 34, "DEV LIVE", C14)
    if pyxel.btnp(pyxel.KEY_C):
        game.enemies.clear(); game.enemy_bullets.clear(); game.player_bullets.clear(); game.rockets.clear(); game.bombs.clear()
        game.add_text(game.player.x + 10, 34, "CLEARED", C7)
    if pyxel.btnp(pyxel.KEY_B):
        _dev_spawn_boss(game)
    if pyxel.btnp(pyxel.KEY_S):
        game.supply_drops.append(SupplyDrop(game.player.x + 90, -60, "field"))
        game.add_text(game.player.x + 10, 34, "SUPPLY INBOUND", C10)
    if pyxel.btnp(pyxel.KEY_R):
        game.reset_run()
        game.dev_mode = True
        game.dev_panel_open = True
def _draw_dev_panel(game):
    _ensure_creative_state(game)
    if not game.dev_mode:
        return
    pyxel.rect(214, 26, 104, 12, C0)
    pyxel.rectb(214, 26, 104, 12, C14)
    pyxel.text(218, 29, "DEV" + (" PANEL" if game.dev_panel_open else " LIVE"), C14)
    pyxel.text(270, 29, "TYPE DEV", C6)
    if not game.dev_panel_open:
        pyxel.text(218, 40, "TYPE DEV: PANEL", C6)
        return
    rows = _dev_rows(game)
    x, y, w, h = 74, 28, 172, 140
    pyxel.rect(x, y, w, h, C0)
    pyxel.rectb(x, y, w, h, C14)
    pyxel.text(x + 8, y + 6, "EXTREME DEV CONSOLE", C14)
    pyxel.text(x + 8, y + 16, "UP/DN SEL  LEFT/RIGHT SLIDE", C6)
    start = max(0, min(game.dev_cursor - 4, max(0, len(rows) - 8)))
    for i in range(start, min(start + 8, len(rows))):
        label, value, lo, hi, step, _apply = rows[i]
        row_y = y + 30 + (i - start) * 12
        selected = i == game.dev_cursor
        if selected:
            pyxel.rect(x + 5, row_y - 2, w - 10, 10, C12)
        col = C0 if selected else C7
        pyxel.text(x + 9, row_y, label, col)
        pyxel.text(x + 56, row_y, str(int(value)), col)
        bar_x = x + 92
        bar_w = 62
        pyxel.rect(bar_x, row_y + 2, bar_w, 3, C5)
        ratio = 0 if hi == lo else (value - lo) / (hi - lo)
        pyxel.rect(bar_x, row_y + 2, int(bar_w * clamp(ratio, 0, 1)), 3, C10 if not selected else C0)
    pyxel.text(x + 8, y + h - 24, "S DROP  B BOSS  C CLEAR  R RESET", C10)
    pyxel.text(x + 8, y + h - 12, "SPACE LIVE   TYPE DEV OFF", C6)
_old_creative_reset_run = Game.reset_run
_old_creative_update = Game.update
_old_creative_update_playing = Game.update_playing
_old_creative_draw_world = Game.draw_world
_old_creative_draw_hud = Game.draw_hud
_old_creative_handle_collisions = Game.handle_collisions
_old_creative_player_hit = Game.player_hit
_old_creative_kill_enemy = Game.kill_enemy
_old_creative_missile_chance = Game.missile_spawn_chance
_old_creative_drone_chance = Game.drone_spawn_chance
_old_creative_turret_chance = Game.turret_spawn_chance
_old_creative_bomber_chance = Game.bomber_spawn_chance
def _creative_reset_run(self):
    _old_creative_reset_run(self)
    self.supply_drops = []
    self.field_order = None
    self.field_order_progress = 0
    self.field_order_timer = 0
    self.field_order_cooldown = 4 * FPS
    self.field_order_start_x = self.player.x
    self.field_order_start_coins = self.coins_total
    self.field_order_kills = 0
    self.field_order_start_kills = 0
    self.field_order_near = 0
    self.field_order_start_near = 0
    self.damage_taken_run = 0
    self.field_order_start_damage = 0
    self.dev_panel_open = getattr(self, "dev_panel_open", False)
    self.dev_cursor = getattr(self, "dev_cursor", 0)
    self.dev_spawn_pct = getattr(self, "dev_spawn_pct", 100)
    self.dev_gravity_pct = getattr(self, "dev_gravity_pct", int(GRAVITY * 100))
    self.dev_move_pct = int(self.player.speed * 10)
    self.dev_jump_pct = int(abs(self.player.jump_power) * 10)
    self.dev_enemy_cap = getattr(self, "dev_enemy_cap", 32)
Game.reset_run = _creative_reset_run
def _creative_update_dev_buffer(self):
    _ensure_creative_state(self)
    keys = [(pyxel.KEY_D, "D"), (pyxel.KEY_E, "E"), (pyxel.KEY_V, "V")]
    for key, ch in keys:
        if pyxel.btnp(key):
            self.dev_buffer += ch
            self.dev_buffer = self.dev_buffer[-8:]
    if "DEV" in self.dev_buffer:
        self.dev_mode = not self.dev_mode
        self.dev_panel_open = self.dev_mode
        self.dev_buffer = ""
        self.add_text(self.player.x + 20, 30, "DEV CONSOLE" if self.dev_mode else "DEV OFF", C14 if self.dev_mode else C8)
        sfx(1, 4)
Game.update_dev_buffer = _creative_update_dev_buffer
def _creative_update(self):
    if getattr(self, "state", "") != "playing":
        try:
            self.update_dev_buffer()
        except Exception:
            pass
    _old_creative_update(self)
Game.update = _creative_update
def _creative_update_playing(self):
    _ensure_creative_state(self)
    self.update_dev_buffer()
    if self.dev_mode and self.dev_panel_open:
        _dev_update_panel(self)
        self.update_shake()
        for e in self.effects:
            e.update()
        for p in self.particles:
            p.update()
        for t in self.texts:
            t.update()
        self.effects = [e for e in self.effects if e.alive]
        self.particles = [p for p in self.particles if p.alive]
        self.texts = [t for t in self.texts if t.alive]
        return
    _old_creative_update_playing(self)
    if self.dev_mode and len(self.enemies) > self.dev_enemy_cap:
        self.enemies = self.enemies[:self.dev_enemy_cap]
    if not hasattr(self, "supply_drops"):
        self.supply_drops = []
    if random.random() < (0.00045 + self.difficulty_value() * 0.00003):
        self.supply_drops.append(SupplyDrop(self.cam_x + SCREEN_W + random.randint(30, 180), -60, "field"))
    for sd in self.supply_drops:
        sd.update(self)
    self.supply_drops = [sd for sd in self.supply_drops if sd.alive]
    _update_field_order(self)
Game.update_playing = _creative_update_playing
def _creative_handle_collisions(self):
    _old_creative_handle_collisions(self)
    _ensure_creative_state(self)
    px, py, pw, ph = self.player.hurtbox()
    for sd in self.supply_drops:
        if not sd.alive:
            continue
        crate_rect = (sd.x, sd.y, sd.w, sd.h)
        if rects_overlap(px, py, pw, ph, *crate_rect):
            sd.open(self, violent=False)
            continue
        for b in self.player_bullets:
            if b.alive and rects_overlap(b.x - 2, b.y - 2, 4, 4, *crate_rect):
                b.alive = False
                sd.hp -= b.dmg
                self.spawn_smoke(b.x, b.y, 2)
                if sd.hp <= 0:
                    sd.open(self, violent=True)
                else:
                    self.add_text(sd.x, sd.y - 8, "CRATE HIT", C7)
                break
Game.handle_collisions = _creative_handle_collisions
def _creative_player_hit(self, dmg=1):
    before = self.player.hp
    _old_creative_player_hit(self, dmg)
    if self.player.hp < before:
        _ensure_creative_state(self)
        self.damage_taken_run += 1
Game.player_hit = _creative_player_hit
def _creative_kill_enemy(self, enemy):
    _ensure_creative_state(self)
    self.field_order_kills += 1
    _old_creative_kill_enemy(self, enemy)
Game.kill_enemy = _creative_kill_enemy
def _creative_draw_world(self):
    _old_creative_draw_world(self)
    _ensure_creative_state(self)
    cam = self.cam_x + self.shake_x
    pyxel.camera(self.shake_x, self.shake_y)
    for sd in self.supply_drops:
        sd.draw(cam)
    pyxel.camera(0, 0)
Game.draw_world = _creative_draw_world
def _creative_draw_hud(self):
    _old_creative_draw_hud(self)
    _ensure_creative_state(self)
    if self.field_order:
        o = self.field_order
        x, y, w = 84, 26, 152
        pyxel.rect(x, y, w, 27, C0)
        pyxel.rectb(x, y, w, 27, o["color"])
        pyxel.text(x + 5, y + 4, "FIELD ORDER: " + o["code"], o["color"])
        pyxel.text(x + 5, y + 13, o["brief"][:24], C7)
        bar_w = 92
        fill = int(bar_w * clamp(self.field_order_progress / max(1, o["goal"]), 0, 1))
        pyxel.rect(x + 5, y + 22, bar_w, 3, C5)
        pyxel.rect(x + 5, y + 22, fill, 3, o["color"])
        pyxel.text(x + 104, y + 20, str(int(self.field_order_progress)) + "/" + str(int(o["goal"])), C7)
        pyxel.text(x + 128, y + 4, str(max(0, self.field_order_timer // FPS)) + "s", C6)
    else:
        if self.field_order_cooldown < 3 * FPS:
            pyxel.text(92, 28, "RADIO STATIC... NEW ORDER SOON", C6)
    _draw_dev_panel(self)
Game.draw_hud = _creative_draw_hud
def _creative_spawn_factor(self):
    _ensure_creative_state(self)
    if self.dev_mode:
        return max(0, self.dev_spawn_pct) / 100.0
    return 1.0
def _creative_missile_spawn_chance(self):
    return _old_creative_missile_chance(self) * _creative_spawn_factor(self)
def _creative_drone_spawn_chance(self):
    return _old_creative_drone_chance(self) * _creative_spawn_factor(self)
def _creative_turret_spawn_chance(self):
    return _old_creative_turret_chance(self) * _creative_spawn_factor(self)
def _creative_bomber_spawn_chance(self):
    return _old_creative_bomber_chance(self) * _creative_spawn_factor(self)
Game.missile_spawn_chance = _creative_missile_spawn_chance
Game.drone_spawn_chance = _creative_drone_spawn_chance
Game.turret_spawn_chance = _creative_turret_spawn_chance
Game.bomber_spawn_chance = _creative_bomber_spawn_chance
_old_creative_update_playing_final = Game.update_playing
def _creative_update_playing_final(self):
    before_near_cd = getattr(self, "near_miss_cooldown", 0)
    _old_creative_update_playing_final(self)
    if getattr(self, "near_miss_cooldown", 0) == 45 and before_near_cd <= 0:
        _ensure_creative_state(self)
        self.field_order_near += 1
Game.update_playing = _creative_update_playing_final
_old_ooo_supply_open = SupplyDrop.open
def _ooo_supply_open(self, game, violent=False):
    if self.opened or not self.alive:
        return
    before_hp = game.player.hp
    _old_ooo_supply_open(self, game, violent)
    if game.player.hp < game.player.max_hp:
        game.player.hp += 1
        heal_text = "+1 HP"
    else:
        heal_text = "HP FULL"
    game.player.invuln = max(game.player.invuln, FPS)
    game.player.green_aura_timer = FPS
    game.add_text(game.player.x + 6, game.player.y - 18, heal_text + "  AURA", C3)
    sfx(1, 3)
SupplyDrop.open = _ooo_supply_open
def _ooo_update_dev_buffer(self):
    _ensure_creative_state(self)
    if getattr(self, "_dev_buffer_frame", -1) == pyxel.frame_count:
        return
    self._dev_buffer_frame = pyxel.frame_count
    if not hasattr(self, "dev_buffer"):
        self.dev_buffer = ""
    if pyxel.btnp(pyxel.KEY_O):
        self.dev_buffer += "O"
        self.dev_buffer = self.dev_buffer[-8:]
    if "OOO" in self.dev_buffer:
        self.dev_mode = not self.dev_mode
        self.dev_panel_open = self.dev_mode
        self.dev_buffer = ""
        self.add_text(
            self.player.x + 20,
            30,
            "OOO DEV CONSOLE" if self.dev_mode else "OOO DEV OFF",
            C14 if self.dev_mode else C8,
        )
        sfx(1, 4)
Game.update_dev_buffer = _ooo_update_dev_buffer
def _dev_set_level(game, v):
    level = int(clamp(v, 1, CAMPAIGN_LEVELS))
    game.level = level
    game.checkpoint_level = level
    game.distance = 0
    game.total_distance = max(getattr(game, "total_distance", 0), (level - 1) * LEVEL_DISTANCE)
    game.terrain.reset()
    game.player.x = 40
    game.player.y = game.terrain.ground_y_at(game.player.x + 8, game.difficulty_value()) - PLAYER_H
    game.player.vx = 0
    game.player.vy = 0
    game.player.on_ground = True
    game.cam_x = 0
    game.enemies.clear()
    game.enemy_bullets.clear()
    game.player_bullets.clear()
    game.rockets.clear()
    game.bombs.clear()
    game.powerups.clear()
    game.coins.clear()
    if hasattr(game, "supply_drops"):
        game.supply_drops.clear()
    if getattr(game, "state", "") in ("menu", "tutorial", "story_update", "game_over", "victory"):
        game.state = "playing"
        stop_story_music()
    game.add_text(game.player.x + 12, 34, "JUMPED TO LEVEL " + str(game.level), C14)
def _draw_dev_panel(game):
    _ensure_creative_state(game)
    if not game.dev_mode:
        return
    pyxel.rect(206, 26, 112, 12, C0)
    pyxel.rectb(206, 26, 112, 12, C14)
    pyxel.text(210, 29, "OOO DEV" + (" PANEL" if game.dev_panel_open else " LIVE"), C14)
    pyxel.text(276, 29, "OOO", C6)
    if not game.dev_panel_open:
        pyxel.text(210, 40, "TYPE OOO: PANEL", C6)
        return
    rows = _dev_rows(game)
    x, y, w, h = 60, 24, 200, 148
    pyxel.rect(x, y, w, h, C0)
    pyxel.rectb(x, y, w, h, C14)
    pyxel.text(x + 8, y + 6, "EXTREME COMPACT DEV CONSOLE", C14)
    pyxel.text(x + 8, y + 16, "UP/DN SELECT   LEFT/RIGHT SLIDE", C6)
    start = max(0, min(game.dev_cursor - 4, max(0, len(rows) - 8)))
    for i in range(start, min(start + 8, len(rows))):
        label, value, lo, hi, step, _apply = rows[i]
        row_y = y + 31 + (i - start) * 12
        selected = i == game.dev_cursor
        if selected:
            pyxel.rect(x + 5, row_y - 2, w - 10, 10, C12)
        col = C0 if selected else C7
        display_label = "PICK LEVEL" if label == "LEVEL" else label
        pyxel.text(x + 9, row_y, display_label, col)
        pyxel.text(x + 70, row_y, str(int(value)), col)
        bar_x = x + 104
        bar_w = 74
        pyxel.rect(bar_x, row_y + 2, bar_w, 3, C5)
        ratio = 0 if hi == lo else (value - lo) / (hi - lo)
        pyxel.rect(bar_x, row_y + 2, int(bar_w * clamp(ratio, 0, 1)), 3, C10 if not selected else C0)
    pyxel.text(x + 8, y + h - 25, "S SUPPLY  B BOSS  C CLEAR  R RESET", C10)
    pyxel.text(x + 8, y + h - 13, "SPACE LIVE   TYPE OOO OFF/ON", C6)
_old_ooo_draw_world = Game.draw_world
def _ooo_draw_world(self):
    _old_ooo_draw_world(self)
    aura = getattr(self.player, "green_aura_timer", 0)
    if aura > 0:
        cam = self.cam_x + self.shake_x
        sx = int(self.player.x - cam)
        sy = int(self.player.y)
        pulse = (pyxel.frame_count // 4) % 3
        pyxel.camera(self.shake_x, self.shake_y)
        pyxel.circb(sx + 8, sy + 11, 16 + pulse, C3)
        pyxel.circb(sx + 8, sy + 11, 12 + pulse, C11)
        for i in range(6):
            a = pyxel.frame_count * 0.12 + i * math.tau / 6
            px = sx + 8 + int(math.cos(a) * (14 + pulse))
            py = sy + 11 + int(math.sin(a) * (14 + pulse))
            pyxel.pset(px, py, C3)
        pyxel.camera(0, 0)
Game.draw_world = _ooo_draw_world
_old_ooo_update_playing = Game.update_playing
def _ooo_update_playing(self):
    _old_ooo_update_playing(self)
    aura = getattr(self.player, "green_aura_timer", 0)
    if aura > 0:
        self.player.green_aura_timer = aura - 1
        self.player.invuln = max(self.player.invuln, self.player.green_aura_timer)
Game.update_playing = _ooo_update_playing
SECRET_WALL_X = 2
SECRET_BUNKER_X = -238
SECRET_BUNKER_W = 124
SECRET_BUNKER_DOOR_X = SECRET_BUNKER_X + 48
SECRET_BUNKER_DOOR_W = 28
def _ensure_secret_bunker_state(game):
    if not hasattr(game, "secret_wall_broken"):
        game.secret_wall_broken = False
    if not hasattr(game, "secret_wall_hp"):
        game.secret_wall_hp = 5
    if not hasattr(game, "secret_wall_flash"):
        game.secret_wall_flash = 0
    if not hasattr(game, "secret_dev_inside"):
        game.secret_dev_inside = False
    if not hasattr(game, "secret_bunker_hint_timer"):
        game.secret_bunker_hint_timer = 0
def _secret_wall_y(game):
    try:
        gy = game.terrain.ground_y_at(14, game.difficulty_value())
    except Exception:
        gy = GROUND_Y
    return gy - 58, gy + 4
def _secret_hit_wall(game, bullet):
    _ensure_secret_bunker_state(game)
    if game.secret_wall_broken:
        return False
    y1, y2 = _secret_wall_y(game)
    bx0 = getattr(bullet, "px", bullet.x)
    bx1 = bullet.x
    by0 = getattr(bullet, "py", bullet.y)
    by1 = bullet.y
    crossed = min(bx0, bx1) <= SECRET_WALL_X <= max(bx0, bx1)
    in_y = (y1 <= by0 <= y2) or (y1 <= by1 <= y2) or (min(by0, by1) <= y2 and max(by0, by1) >= y1)
    return crossed and in_y and getattr(bullet, "friendly", True)
def _damage_secret_wall(game, bullet):
    _ensure_secret_bunker_state(game)
    if game.secret_wall_broken:
        return
    game.secret_wall_hp -= max(1, getattr(bullet, "dmg", 1))
    game.secret_wall_flash = 12
    bullet.alive = False
    y1, y2 = _secret_wall_y(game)
    hit_y = clamp(getattr(bullet, "y", (y1 + y2) / 2), y1 + 8, y2 - 8)
    game.effects.append(Explosion(SECRET_WALL_X, hit_y, 10, "muzzle"))
    game.spawn_particles(SECRET_WALL_X, hit_y, 18)
    sfx(1, 1)
    if game.secret_wall_hp <= 0:
        game.secret_wall_broken = True
        game.secret_wall_flash = 40
        game.effects.append(Explosion(SECRET_WALL_X - 2, (y1 + y2) / 2, 26, "normal"))
        for yy in range(int(y1) + 8, int(y2), 10):
            game.spawn_particles(SECRET_WALL_X - 4, yy, 10)
        game.add_text(20, y1 - 8, "HIDDEN WALL BREACHED", C14)
        sfx(2, 2)
def _draw_secret_wall(game):
    _ensure_secret_bunker_state(game)
    y1, y2 = _secret_wall_y(game)
    sx = int(SECRET_WALL_X - game.cam_x)
    if -20 <= sx <= SCREEN_W + 20 and not game.secret_wall_broken:
        if game.secret_wall_flash > 0 or (game.player.x < 72 and (pyxel.frame_count // 28) % 2 == 0):
            col = C14 if game.secret_wall_flash > 0 else C5
            pyxel.line(sx, int(y1), sx, int(y2), col)
            pyxel.line(sx + 1, int(y1) + 8, sx + 1, int(y1) + 22, C12)
            pyxel.line(sx - 1, int(y1) + 28, sx - 1, int(y1) + 44, C12)
            pyxel.pset(sx + 2, int(y1) + 12, C7)
            pyxel.pset(sx - 2, int(y1) + 37, C7)
    elif game.secret_wall_broken:
        sx = int(SECRET_WALL_X - game.cam_x)
        if -30 <= sx <= SCREEN_W + 30:
            for i in range(5):
                pyxel.pset(sx + random.randint(-3, 4), int(y1) + 8 + i * 9, C5)
def _draw_secret_dev_bunker(game):
    _ensure_secret_bunker_state(game)
    gy = game.terrain.ground_y_at(SECRET_BUNKER_X + 62, game.difficulty_value())
    sx = int(SECRET_BUNKER_X - game.cam_x)
    if sx < -170 or sx > SCREEN_W + 80:
        return
    pulse = (pyxel.frame_count // 6) % 2
    glow = C11 if pulse else C3
    neon = C14 if pulse else C12
    pyxel.rect(sx + 10, gy - 52, 104, 52, C0)
    pyxel.rectb(sx + 10, gy - 52, 104, 52, C5)
    pyxel.rect(sx + 17, gy - 45, 90, 7, C4)
    pyxel.line(sx + 10, gy - 52, sx + 114, gy - 52, C7)
    pyxel.line(sx + 14, gy - 56, sx + 110, gy - 56, C6)
    pyxel.line(sx + 20, gy - 60, sx + 104, gy - 60, C5)
    door_x = sx + 48
    pyxel.rect(door_x, gy - 31, 28, 31, C1)
    pyxel.rectb(door_x, gy - 31, 28, 31, neon)
    pyxel.rect(door_x + 5, gy - 25, 18, 4, glow)
    pyxel.text(door_x + 6, gy - 18, "DEV", C7)
    pyxel.pset(door_x + 24, gy - 7, C10)
    pyxel.line(sx + 24, gy - 52, sx + 24, gy - 75, C6)
    pyxel.circb(sx + 24, gy - 78, 8, neon)
    pyxel.line(sx + 24, gy - 78, sx + 34, gy - 84, C7)
    pyxel.line(sx + 34, gy - 84, sx + 41, gy - 80, C14)
    pyxel.line(sx + 88, gy - 52, sx + 99, gy - 67, C6)
    pyxel.tri(sx + 99, gy - 67, sx + 116, gy - 72, sx + 106, gy - 55, C12)
    pyxel.circb(sx + 106, gy - 64, 5, C14)
    labels = ["LV", "HP", "SP", "GR"]
    for i, label in enumerate(labels):
        yy = gy - 37 + i * 8
        pyxel.text(sx + 20, yy, label, C6)
        pyxel.rect(sx + 34, yy + 2, 22, 2, C5)
        knob = sx + 34 + ((pyxel.frame_count // (5 + i)) + i * 6) % 22
        pyxel.rect(knob, yy + 1, 3, 4, C10 if i % 2 else C14)
    for i in range(4):
        pyxel.rect(sx + 82 + i * 5, gy - 40, 3, 18, C4)
        if (pyxel.frame_count + i * 3) % 16 < 8:
            pyxel.pset(sx + 83 + i * 5, gy - 44, C10)
    pyxel.rect(sx + 18, gy - 16, 18, 10, C3)
    pyxel.rectb(sx + 18, gy - 16, 18, 10, C11)
    pyxel.text(sx + 21, gy - 13, "CORE", C7)
    if abs((game.player.x + 8) - (SECRET_BUNKER_DOOR_X + SECRET_BUNKER_DOOR_W / 2)) < 30:
        pyxel.rect(sx + 18, gy - 72, 86, 10, C0)
        pyxel.rectb(sx + 18, gy - 72, 86, 10, C14)
        pyxel.text(sx + 22, gy - 69, "PRESS W: ENTER DEV BUILD", C7)
def _try_enter_secret_dev_bunker(game):
    _ensure_secret_bunker_state(game)
    if not game.secret_wall_broken:
        return
    door_l = SECRET_BUNKER_DOOR_X
    door_r = SECRET_BUNKER_DOOR_X + SECRET_BUNKER_DOOR_W
    px = game.player.x + PLAYER_W / 2
    gy = game.terrain.ground_y_at(SECRET_BUNKER_X + 62, game.difficulty_value())
    near_door = door_l <= px <= door_r and game.player.y + PLAYER_H >= gy - 6
    if near_door:
        game.secret_bunker_hint_timer = 30
        if pyxel.btnp(pyxel.KEY_W) or pyxel.btnp(pyxel.KEY_UP):
            game.secret_dev_inside = True
            game.dev_mode = True
            game.dev_panel_open = True
            game.dev_buffer = ""
            game.enemies.clear(); game.enemy_bullets.clear(); game.player_bullets.clear(); game.rockets.clear(); game.bombs.clear()
            game.add_text(game.player.x + 8, game.player.y - 12, "DEV BUILD BUNKER ONLINE", C14)
            sfx(1, 4)
_old_secret_player_update = Player.update
def _secret_player_update(self, difficulty, dev_mode=False):
    _old_secret_player_update(self, difficulty, dev_mode)
    game = globals().get("_active_secret_game")
    if game is not None:
        _ensure_secret_bunker_state(game)
        if game.secret_wall_broken:
            self.x = max(SECRET_BUNKER_X - 18, self.x)
        else:
            self.x = max(0, self.x)
Player.update = _secret_player_update
_old_secret_game_update_playing = Game.update_playing
def _secret_game_update_playing(self):
    globals()["_active_secret_game"] = self
    _ensure_secret_bunker_state(self)
    _old_secret_game_update_playing(self)
    if self.secret_wall_flash > 0:
        self.secret_wall_flash -= 1
    for b in list(getattr(self, "player_bullets", [])):
        if getattr(b, "alive", False) and _secret_hit_wall(self, b):
            _damage_secret_wall(self, b)
    _try_enter_secret_dev_bunker(self)
Game.update_playing = _secret_game_update_playing
_old_secret_draw_world = Game.draw_world
def _secret_draw_world(self):
    _old_secret_draw_world(self)
    pyxel.camera(self.shake_x, self.shake_y)
    _draw_secret_dev_bunker(self)
    _draw_secret_wall(self)
    pyxel.camera(0, 0)
Game.draw_world = _secret_draw_world
_old_secret_draw_hud = Game.draw_hud
def _secret_draw_hud(self):
    _old_secret_draw_hud(self)
    _ensure_secret_bunker_state(self)
    if self.secret_wall_broken and not self.dev_mode and self.player.x < 32:
        pyxel.rect(52, 182, 220, 12, C0)
        pyxel.rectb(52, 182, 220, 12, C14)
        pyxel.text(58, 185, "SECRET ROUTE OPEN: WALK LEFT TO THE DEV BUILD BUNKER", C7)
    elif not self.secret_wall_broken and self.player.x < 95:
        pyxel.text(8, 188, "RUMOR: LEFT OF FIRST BUNKER SOUNDS HOLLOW", C5)
Game.draw_hud = _secret_draw_hud
WALK_LEFT_DEV_ENTRY_X = SECRET_BUNKER_DOOR_X + SECRET_BUNKER_DOOR_W // 2
def _ensure_secret_bunker_state(game):
    game.secret_wall_broken = True
    if not hasattr(game, "secret_wall_hp"):
        game.secret_wall_hp = 0
    if not hasattr(game, "secret_wall_flash"):
        game.secret_wall_flash = 0
    if not hasattr(game, "secret_dev_inside"):
        game.secret_dev_inside = False
    if not hasattr(game, "secret_bunker_hint_timer"):
        game.secret_bunker_hint_timer = 0
_old_walkleft_camera = Game.update_camera
def _walkleft_update_camera(self):
    _ensure_secret_bunker_state(self)
    target = self.player.x - SCREEN_W // 3
    if self.is_survival() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "SURVIVAL ROTATION"):
        target = self.player.x - SCREEN_W // 2
    left_limit = SECRET_BUNKER_X - 42
    if target < left_limit:
        target = left_limit
    self.cam_x += (target - self.cam_x) * 0.14
Game.update_camera = _walkleft_update_camera
_old_walkleft_player_update = Player.update
def _walkleft_player_update(self, difficulty, dev_mode=False):
    prev_x = self.x
    _old_walkleft_player_update(self, difficulty, dev_mode)
    game = globals().get("_active_secret_game")
    if game is None:
        return
    _ensure_secret_bunker_state(game)
    left_floor = SECRET_BUNKER_X - 28
    moving_left = pyxel.btn(pyxel.KEY_A)
    moving_right = pyxel.btn(pyxel.KEY_D)
    if prev_x < 0 or self.x <= 1:
        x = prev_x if prev_x < 0 else self.x
        move = self.speed if self.on_ground else self.air_speed
        if moving_left:
            x -= move
            self.facing = -1
        if moving_right:
            x += move
            self.facing = 1
        self.x = clamp(x, left_floor, LEVEL_DISTANCE + 200)
        foot = self.x + PLAYER_W / 2
        if not self.terrain.is_gap_at(foot, difficulty):
            gy = self.terrain.ground_y_at(foot, difficulty)
            if self.y + PLAYER_H >= gy - 8 or self.on_ground:
                self.y = gy - PLAYER_H
                self.vy = 0
                self.on_ground = True
                self.jump_count = 0
Player.update = _walkleft_player_update
def _try_enter_secret_dev_bunker(game):
    _ensure_secret_bunker_state(game)
    px = game.player.x + PLAYER_W / 2
    gy = game.terrain.ground_y_at(SECRET_BUNKER_X + 62, game.difficulty_value())
    close_to_door = px <= WALK_LEFT_DEV_ENTRY_X + 10 and game.player.y + PLAYER_H >= gy - 12
    if close_to_door and not getattr(game, "secret_dev_inside", False):
        game.secret_dev_inside = True
        game.dev_mode = True
        game.dev_panel_open = True
        game.dev_buffer = ""
        game.enemies.clear()
        game.enemy_bullets.clear()
        game.player_bullets.clear()
        game.rockets.clear()
        game.bombs.clear()
        game.add_text(game.player.x + 8, game.player.y - 12, "DEV BUILD BUNKER ONLINE", C14)
        sfx(1, 4)
def _secret_draw_hud(self):
    _old_secret_draw_hud(self)
    _ensure_secret_bunker_state(self)
    if not self.dev_mode and self.player.x < 100:
        pyxel.rect(44, 182, 234, 12, C0)
        pyxel.rectb(44, 182, 234, 12, C14)
        pyxel.text(51, 185, "SECRET DEV ROUTE: JUST WALK LEFT", C7)
    elif self.dev_mode:
        pyxel.text(8, 188, "DEV BUILD ACTIVE", C14)
Game.draw_hud = _secret_draw_hud
def _draw_secret_dev_bunker(game):
    _ensure_secret_bunker_state(game)
    gy = game.terrain.ground_y_at(SECRET_BUNKER_X + 62, game.difficulty_value())
    sx = int(SECRET_BUNKER_X - game.cam_x)
    if sx < -170 or sx > SCREEN_W + 80:
        return
    pulse = (pyxel.frame_count // 6) % 2
    glow = C11 if pulse else C3
    neon = C14 if pulse else C12
    pyxel.rect(sx + 10, gy - 52, 104, 52, C0)
    pyxel.rectb(sx + 10, gy - 52, 104, 52, C5)
    pyxel.rect(sx + 17, gy - 45, 90, 7, C4)
    pyxel.line(sx + 10, gy - 52, sx + 114, gy - 52, C7)
    pyxel.line(sx + 14, gy - 56, sx + 110, gy - 56, C6)
    pyxel.line(sx + 20, gy - 60, sx + 104, gy - 60, C5)
    door_x = sx + 48
    pyxel.rect(door_x, gy - 31, 28, 31, C1)
    pyxel.rectb(door_x, gy - 31, 28, 31, neon)
    pyxel.rect(door_x + 5, gy - 25, 18, 4, glow)
    pyxel.text(door_x + 6, gy - 18, "DEV", C7)
    pyxel.pset(door_x + 24, gy - 7, C10)
    pyxel.line(sx + 24, gy - 52, sx + 24, gy - 75, C6)
    pyxel.circb(sx + 24, gy - 78, 8, neon)
    pyxel.line(sx + 24, gy - 78, sx + 34, gy - 84, C7)
    pyxel.line(sx + 34, gy - 84, sx + 41, gy - 80, C14)
    pyxel.line(sx + 88, gy - 52, sx + 99, gy - 67, C6)
    pyxel.tri(sx + 99, gy - 67, sx + 116, gy - 72, sx + 106, gy - 55, C12)
    pyxel.circb(sx + 106, gy - 64, 5, C14)
    labels = ["LV", "HP", "SP", "GR"]
    for i, label in enumerate(labels):
        yy = gy - 37 + i * 8
        pyxel.text(sx + 20, yy, label, C6)
        pyxel.rect(sx + 34, yy + 2, 22, 2, C5)
        knob = sx + 34 + ((pyxel.frame_count // (5 + i)) + i * 6) % 22
        pyxel.rect(knob, yy + 1, 3, 4, C10 if i % 2 else C14)
    for i in range(4):
        pyxel.rect(sx + 82 + i * 5, gy - 40, 3, 18, C4)
        if (pyxel.frame_count + i * 3) % 16 < 8:
            pyxel.pset(sx + 83 + i * 5, gy - 44, C10)
    pyxel.rect(sx + 18, gy - 16, 18, 10, C3)
    pyxel.rectb(sx + 18, gy - 16, 18, 10, C11)
    pyxel.text(sx + 21, gy - 13, "CORE", C7)
    if abs((game.player.x + 8) - (SECRET_BUNKER_DOOR_X + SECRET_BUNKER_DOOR_W / 2)) < 42:
        pyxel.rect(sx + 16, gy - 72, 91, 10, C0)
        pyxel.rectb(sx + 16, gy - 72, 91, 10, C14)
        pyxel.text(sx + 20, gy - 69, "AUTO ENTERING DEV BUILD", C7)
EMPIRE_CAMPAIGN_LEVELS = 10
EMPIRE_NAMES = {
    "usa": "AMERICAN EMPIRE",
    "mexico": "AZTEC REPUBLIC",
    "china": "CELESTIAL DYNASTY",
    "germany": "IRON KAISERREICH",
    "serbia": "BALKAN EMPIRE",
    "iran": "PERSIAN DYNASTY",
    "israel": "JERUSALEM COMMAND",
    "antarctica": "POLAR DOMINION",
    "italy": "ROMAN EMPIRE",
    "russia": "EASTERN TSARDOM",
    "poland": "WINGED COMMONWEALTH",
    "france": "NAPOLEONIC EMPIRE",
    "nigeria": "NIGER DELTA KINGDOM",
    "switzerland": "ALPINE CONFEDERACY",
    "canada": "NORTHERN DOMINION",
    "ukraine": "COSSACK HETMANATE",
    "turkiye": "ANATOLIAN SULTANATE",
    "south_korea": "SEOUL DYNASTY",
    "north_korea": "RED DYNASTY",
    "saudi": "DESERT CALIPHATE",
    "belgium": "LOWLAND EMPIRE",
    "austria": "HABSBURG REALM",
    "romania": "DACIAN EMPIRE",
    "japan": "RISING SUN SHOGUNATE",
    "uk": "BRITISH EMPIRE",
    "spain": "IBERIAN EMPIRE",
    "brazil": "BRAZILIAN EMPIRE",
    "australia": "SOUTHERN COMMONWEALTH",
    "south_africa": "CAPE DOMINION",
}
def _empire_name(game):
    return EMPIRE_NAMES.get(game.current_country()["key"], game.current_country()["name"] + " EMPIRE")
def _ensure_empire_state(game):
    if not hasattr(game, "empire_mode"):
        game.empire_mode = False
    if not hasattr(game, "empire_won"):
        game.empire_won = False
    if not hasattr(game, "ooo_route_unlocked"):
        game.ooo_route_unlocked = False
    if not hasattr(game, "dev_drag_row"):
        game.dev_drag_row = -1
    if not hasattr(game, "dev_knob_pulse"):
        game.dev_knob_pulse = 0
def _ooo_secret_update_dev_buffer(self):
    _ensure_creative_state(self)
    _ensure_empire_state(self)
    if getattr(self, "_ooo_unlock_frame", -1) == pyxel.frame_count:
        return
    self._ooo_unlock_frame = pyxel.frame_count
    if not hasattr(self, "dev_buffer"):
        self.dev_buffer = ""
    if pyxel.btnp(pyxel.KEY_O):
        self.dev_buffer += "O"
        self.dev_buffer = self.dev_buffer[-3:]
    if self.dev_buffer == "OOO":
        self.ooo_route_unlocked = True
        self.secret_wall_broken = True
        self.dev_buffer = ""
        sfx(1, 4)
Game.update_dev_buffer = _ooo_secret_update_dev_buffer
def _ensure_secret_bunker_state(game):
    _ensure_empire_state(game)
    unlocked = bool(getattr(game, "ooo_route_unlocked", False))
    game.secret_wall_broken = unlocked
    if not hasattr(game, "secret_wall_hp"):
        game.secret_wall_hp = 0
    if not hasattr(game, "secret_wall_flash"):
        game.secret_wall_flash = 0
    if not hasattr(game, "secret_dev_inside"):
        game.secret_dev_inside = False
    if not hasattr(game, "secret_bunker_hint_timer"):
        game.secret_bunker_hint_timer = 0
_old_empire_init = Game.__init__
def _empire_init(self):
    real_init = pyxel.init
    def init_then_mouse(*args, **kwargs):
        result = real_init(*args, **kwargs)
        try:
            pyxel.mouse(True)
        except Exception:
            pass
        return result
    pyxel.init = init_then_mouse
    try:
        _old_empire_init(self)
    finally:
        pyxel.init = real_init
Game.__init__ = _empire_init
def _walkleft_update_camera(self):
    _ensure_secret_bunker_state(self)
    target = self.player.x - SCREEN_W // 3
    if self.is_survival() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "SURVIVAL ROTATION"):
        target = self.player.x - SCREEN_W // 2
    if getattr(self, "ooo_route_unlocked", False):
        left_limit = SECRET_BUNKER_X - 42
        if target < left_limit:
            target = left_limit
    else:
        target = max(0, target)
    self.cam_x += (target - self.cam_x) * 0.14
Game.update_camera = _walkleft_update_camera
def _walkleft_player_update(self, difficulty, dev_mode=False):
    prev_x = self.x
    _old_walkleft_player_update(self, difficulty, dev_mode)
    game = globals().get("_active_secret_game")
    if game is None:
        return
    _ensure_secret_bunker_state(game)
    if not getattr(game, "ooo_route_unlocked", False):
        self.x = max(0, self.x)
        return
    left_floor = SECRET_BUNKER_X - 28
    moving_left = pyxel.btn(pyxel.KEY_A)
    moving_right = pyxel.btn(pyxel.KEY_D)
    if prev_x < 0 or self.x <= 1:
        x = prev_x if prev_x < 0 else self.x
        move = self.speed if self.on_ground else self.air_speed
        if moving_left:
            x -= move
            self.facing = -1
        if moving_right:
            x += move
            self.facing = 1
        self.x = clamp(x, left_floor, LEVEL_DISTANCE + 200)
        foot = self.x + PLAYER_W / 2
        if not self.terrain.is_gap_at(foot, difficulty):
            gy = self.terrain.ground_y_at(foot, difficulty)
            if self.y + PLAYER_H >= gy - 8 or self.on_ground:
                self.y = gy - PLAYER_H
                self.vy = 0
                self.on_ground = True
                self.jump_count = 0
Player.update = _walkleft_player_update
def _try_enter_secret_dev_bunker(game):
    _ensure_secret_bunker_state(game)
    if not getattr(game, "ooo_route_unlocked", False):
        return
    px = game.player.x + PLAYER_W / 2
    gy = game.terrain.ground_y_at(SECRET_BUNKER_X + 62, game.difficulty_value())
    close_to_door = px <= WALK_LEFT_DEV_ENTRY_X + 10 and game.player.y + PLAYER_H >= gy - 12
    if close_to_door and not getattr(game, "secret_dev_inside", False):
        game.secret_dev_inside = True
        game.dev_mode = True
        game.dev_panel_open = True
        game.dev_buffer = ""
        game.enemies.clear(); game.enemy_bullets.clear(); game.player_bullets.clear(); game.rockets.clear(); game.bombs.clear()
        sfx(1, 4)
def _draw_secret_dev_bunker(game):
    _ensure_secret_bunker_state(game)
    if not getattr(game, "ooo_route_unlocked", False):
        return
    gy = game.terrain.ground_y_at(SECRET_BUNKER_X + 62, game.difficulty_value())
    sx = int(SECRET_BUNKER_X - game.cam_x)
    if sx < -180 or sx > SCREEN_W + 80:
        return
    pulse = (pyxel.frame_count // 6) % 2
    glow = C11 if pulse else C3
    neon = C14 if pulse else C12
    pyxel.rect(sx + 5, gy - 58, 114, 58, C0)
    pyxel.rectb(sx + 5, gy - 58, 114, 58, C5)
    pyxel.line(sx + 5, gy - 58, sx + 119, gy - 58, C7)
    pyxel.line(sx + 10, gy - 63, sx + 114, gy - 63, C6)
    pyxel.line(sx + 18, gy - 68, sx + 106, gy - 68, C5)
    for i in range(7):
        pyxel.rect(sx + 12 + i * 14, gy - 54, 8, 5, C4)
        if (pyxel.frame_count + i * 4) % 18 < 9:
            pyxel.line(sx + 13 + i * 14, gy - 52, sx + 18 + i * 14, gy - 52, neon)
    door_x = sx + 47
    pyxel.rect(door_x, gy - 35, 30, 35, C1)
    pyxel.rectb(door_x, gy - 35, 30, 35, neon)
    pyxel.rect(door_x + 5, gy - 29, 20, 4, glow)
    pyxel.text(door_x + 7, gy - 20, "DEV", C7)
    for i in range(4):
        pyxel.pset(door_x + 6 + i * 5, gy - 9 + ((pyxel.frame_count + i) % 3), C10)
    pyxel.line(sx + 25, gy - 58, sx + 25, gy - 82, C6)
    pyxel.circb(sx + 25, gy - 85, 9, neon)
    pyxel.line(sx + 25, gy - 85, sx + 38, gy - 92, C7)
    pyxel.line(sx + 38, gy - 92, sx + 48, gy - 87, C14)
    pyxel.line(sx + 89, gy - 58, sx + 103, gy - 76, C6)
    pyxel.tri(sx + 103, gy - 76, sx + 122, gy - 82, sx + 110, gy - 61, C12)
    pyxel.circb(sx + 110, gy - 71, 6, C14)
    labels = ["LV", "HP", "SP", "GR"]
    for i, label in enumerate(labels):
        yy = gy - 43 + i * 8
        pyxel.text(sx + 17, yy, label, C6)
        pyxel.rect(sx + 32, yy + 2, 27, 2, C5)
        knob = sx + 32 + ((pyxel.frame_count // (5 + i)) + i * 7) % 27
        pyxel.rect(knob, yy, 3, 5, C10 if i % 2 else C14)
    pyxel.rect(sx + 82, gy - 40, 25, 19, C3)
    pyxel.rectb(sx + 82, gy - 40, 25, 19, C11)
    pyxel.text(sx + 87, gy - 33, "CORE", C7)
    pyxel.line(sx + 13, gy - 5, sx + 113, gy - 5, C14)
def _dev_rows(game):
    _ensure_empire_state(game)
    max_level = EMPIRE_CAMPAIGN_LEVELS if game.empire_mode else CAMPAIGN_LEVELS
    return [
        ("LEVEL", game.level, 1, max_level, 1, lambda v: _dev_set_level(game, v)),
        ("DIST", int(game.distance), 0, LEVEL_DISTANCE - 40, 40, lambda v: setattr(game, "distance", v)),
        ("HP", game.player.hp, 1, 30, 1, lambda v: _dev_set_hp(game, v)),
        ("RKT", game.player.rockets, 0, 99, 1, lambda v: setattr(game.player, "rockets", int(v))),
        ("BMB", game.player.bombs, 0, 99, 1, lambda v: setattr(game.player, "bombs", int(v))),
        ("COIN", game.coins_total, 0, 9999, 25, lambda v: setattr(game, "coins_total", int(v))),
        ("SPAWN", game.dev_spawn_pct, 0, 300, 10, lambda v: setattr(game, "dev_spawn_pct", int(v))),
        ("GRAV", game.dev_gravity_pct, 0, 90, 2, lambda v: _dev_set_gravity(game, v)),
        ("MOVE", game.dev_move_pct, 5, 50, 1, lambda v: _dev_set_move(game, v)),
        ("JUMP", game.dev_jump_pct, 20, 100, 2, lambda v: _dev_set_jump(game, v)),
        ("CAP", game.dev_enemy_cap, 0, 80, 2, lambda v: setattr(game, "dev_enemy_cap", int(v))),
        ("EMPIRE", 1 if game.empire_mode else 0, 0, 1, 1, lambda v: _dev_set_empire(game, v)),
    ]
def _dev_set_empire(game, v):
    _ensure_empire_state(game)
    game.empire_mode = bool(int(v))
    if game.empire_mode:
        _enter_empire_campaign(game, from_dev=True)
    else:
        game.player.max_hp = max(5 + game.extra_hp_bonus, game.player.max_hp)
        game.player.hp = min(game.player.max_hp, max(1, game.player.hp))
def _dev_set_level(game, v):
    _ensure_empire_state(game)
    max_level = EMPIRE_CAMPAIGN_LEVELS if game.empire_mode else CAMPAIGN_LEVELS
    level = int(clamp(v, 1, max_level))
    game.level = level
    game.checkpoint_level = level
    game.distance = 0
    game.total_distance = max(getattr(game, "total_distance", 0), (level - 1) * LEVEL_DISTANCE)
    game.terrain.reset()
    game.player.x = 40
    game.player.y = game.terrain.ground_y_at(game.player.x + 8, game.difficulty_value()) - PLAYER_H
    game.player.vx = 0; game.player.vy = 0; game.player.on_ground = True
    if game.empire_mode:
        game.player.max_hp = 1
        game.player.hp = 1
    game.cam_x = 0
    game.enemies.clear(); game.enemy_bullets.clear(); game.player_bullets.clear(); game.rockets.clear(); game.bombs.clear(); game.powerups.clear(); game.coins.clear()
    if hasattr(game, "supply_drops"):
        game.supply_drops.clear()
    if getattr(game, "state", "") in ("menu", "tutorial", "story_update", "game_over", "victory"):
        game.state = "playing"
        stop_story_music()
def _set_dev_row_from_mouse(game, row_index, mx, bar_x, bar_w):
    rows = _dev_rows(game)
    if not (0 <= row_index < len(rows)):
        return
    label, value, lo, hi, step, apply = rows[row_index]
    ratio = clamp((mx - bar_x) / max(1, bar_w), 0, 1)
    raw = lo + ratio * (hi - lo)
    if step > 1:
        raw = round(raw / step) * step
    else:
        raw = round(raw)
    apply(int(clamp(raw, lo, hi)))
def _dev_update_panel(game):
    _ensure_creative_state(game)
    _ensure_empire_state(game)
    rows = _dev_rows(game)
    if pyxel.btnp(pyxel.KEY_UP):
        game.dev_cursor = (game.dev_cursor - 1) % len(rows); sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_DOWN):
        game.dev_cursor = (game.dev_cursor + 1) % len(rows); sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_LEFT):
        _apply_dev_delta(game, -1)
    if pyxel.btnp(pyxel.KEY_RIGHT):
        _apply_dev_delta(game, 1)
    mx, my = pyxel.mouse_x, pyxel.mouse_y
    mouse_btn = getattr(pyxel, "MOUSE_BUTTON_LEFT", 0)
    panel_x, panel_y = 38, 20
    start = max(0, min(game.dev_cursor - 4, max(0, len(rows) - 9)))
    bar_x, bar_w = panel_x + 118, 112
    if pyxel.btnp(mouse_btn):
        game.dev_drag_row = -1
        for i in range(start, min(start + 9, len(rows))):
            row_y = panel_y + 40 + (i - start) * 13
            if bar_x - 7 <= mx <= bar_x + bar_w + 7 and row_y - 5 <= my <= row_y + 8:
                game.dev_cursor = i
                game.dev_drag_row = i
                _set_dev_row_from_mouse(game, i, mx, bar_x, bar_w)
                sfx(1, 4)
                break
    if pyxel.btn(mouse_btn) and getattr(game, "dev_drag_row", -1) >= 0:
        _set_dev_row_from_mouse(game, game.dev_drag_row, mx, bar_x, bar_w)
    if not pyxel.btn(mouse_btn):
        game.dev_drag_row = -1
    if pyxel.btnp(pyxel.KEY_SPACE):
        game.dev_panel_open = False
    if pyxel.btnp(pyxel.KEY_C):
        game.enemies.clear(); game.enemy_bullets.clear(); game.player_bullets.clear(); game.rockets.clear(); game.bombs.clear()
    if pyxel.btnp(pyxel.KEY_B):
        _dev_spawn_boss(game)
    if pyxel.btnp(pyxel.KEY_S):
        game.supply_drops.append(SupplyDrop(game.player.x + 90, -60, "field"))
    if pyxel.btnp(pyxel.KEY_R):
        game.reset_run(); game.dev_mode = True; game.dev_panel_open = True
def _draw_dev_panel(game):
    _ensure_creative_state(game)
    _ensure_empire_state(game)
    if not game.dev_mode:
        return
    rows = _dev_rows(game)
    pyxel.rect(214, 16, 102, 14, C0)
    pyxel.rectb(214, 16, 102, 14, C14)
    pyxel.text(219, 20, "DEV BUNKER ONLINE", C14)
    if not game.dev_panel_open:
        return
    x, y, w, h = 34, 18, 252, 164
    pulse = (pyxel.frame_count // 6) % 2
    neon = C14 if pulse else C12
    pyxel.rect(x, y, w, h, C0)
    pyxel.rectb(x, y, w, h, neon)
    pyxel.line(x + 6, y + 5, x + w - 7, y + 5, C5)
    pyxel.line(x + 6, y + h - 6, x + w - 7, y + h - 6, C5)
    pyxel.text(x + 10, y + 10, "UNDERGROUND DEV BUILD TERMINAL", C14)
    pyxel.text(x + 10, y + 20, "DRAG KNOBS  |  S DROP  B BOSS  C CLEAR  R RESET", C6)
    pyxel.rect(x + w - 28, y + 35, 16, h - 55, C1)
    pyxel.rectb(x + w - 28, y + 35, 16, h - 55, C12)
    for yy in range(y + 41, y + h - 28, 11):
        pyxel.rect(x + w - 24, yy, 8, 3, C3 if (yy + pyxel.frame_count) % 22 < 11 else C11)
    start = max(0, min(game.dev_cursor - 4, max(0, len(rows) - 9)))
    bar_x = x + 118
    bar_w = 112
    for i in range(start, min(start + 9, len(rows))):
        label, value, lo, hi, step, _apply = rows[i]
        row_y = y + 40 + (i - start) * 13
        selected = i == game.dev_cursor
        if selected:
            pyxel.rect(x + 7, row_y - 4, w - 42, 11, C1)
            pyxel.rectb(x + 7, row_y - 4, w - 42, 11, C14)
        label2 = "PICK LV" if label == "LEVEL" else label
        pyxel.text(x + 13, row_y, label2, C14 if selected else C7)
        pyxel.text(x + 73, row_y, str(int(value)), C10 if selected else C6)
        pyxel.rect(bar_x, row_y + 2, bar_w, 3, C5)
        ratio = 0 if hi == lo else (value - lo) / (hi - lo)
        fill = int(bar_w * clamp(ratio, 0, 1))
        pyxel.rect(bar_x, row_y + 2, fill, 3, C3 if label == "EMPIRE" and value else C10)
        knob_x = bar_x + fill
        pyxel.rect(knob_x - 2, row_y - 1, 5, 8, C14 if selected else C7)
        pyxel.pset(knob_x, row_y + 3, C0)
    pyxel.text(x + 10, y + h - 18, "SPACE: LIVE PANEL  |  MOUSE: PULL SLIDERS  |  ARROWS: BACKUP", C6)
def _secret_draw_hud(self):
    _old_secret_draw_hud(self)
Game.draw_hud = _secret_draw_hud
def _enter_empire_campaign(game, from_dev=False):
    _ensure_empire_state(game)
    game.empire_mode = True
    game.empire_won = False
    game.level = 1
    game.distance = 0
    game.checkpoint_level = 1
    game.checkpoint_x = 40
    game.terrain.reset()
    game.player.reset(40, game.difficulty_value())
    game.player.max_hp = 1
    game.player.hp = 1
    game.player.invuln = max(game.player.invuln, FPS)
    game.player_bullets.clear(); game.enemy_bullets.clear(); game.rockets.clear(); game.bombs.clear(); game.enemies.clear(); game.effects.clear(); game.particles.clear(); game.texts.clear(); game.powerups.clear(); game.coins.clear()
    if hasattr(game, "supply_drops"):
        game.supply_drops.clear()
    game.cam_x = 0
    game.state = "playing" if from_dev else "story_update"
    game.intro_timer = 0
    stop_story_music()
def _empire_next_level(self):
    _ensure_empire_state(self)
    if not self.empire_mode:
        if self.level >= CAMPAIGN_LEVELS:
            _enter_empire_campaign(self, from_dev=False)
            self.story_progress_index += 1
            play_story_music()
            return
        return _old_empire_next_level(self)
    self.level += 1
    self.distance = 0
    self.checkpoint_level = self.level
    self.checkpoint_x = self.player.x + 30
    self.terrain.reset()
    self.player_bullets.clear(); self.enemy_bullets.clear(); self.rockets.clear(); self.bombs.clear(); self.enemies.clear(); self.effects.clear(); self.particles.clear(); self.texts.clear(); self.powerups.clear(); self.coins.clear()
    if hasattr(self, "supply_drops"):
        self.supply_drops.clear()
    if self.level > EMPIRE_CAMPAIGN_LEVELS:
        self.empire_won = True
        self.state = "victory"
        play_story_music()
    else:
        self.player.reset(self.checkpoint_x, self.difficulty_value())
        self.player.max_hp = 1
        self.player.hp = 1
        self.story_progress_index += 1
        self.state = "story_update"
        self.intro_timer = 0
        play_story_music()
_old_empire_next_level = Game.next_level
Game.next_level = _empire_next_level
_old_empire_respawn = Game.respawn_or_game_over
def _empire_respawn_or_game_over(self):
    _ensure_empire_state(self)
    _old_empire_respawn(self)
    if self.empire_mode and self.state != "game_over":
        self.player.max_hp = 1
        self.player.hp = 1
Game.respawn_or_game_over = _empire_respawn_or_game_over
_old_empire_draw_story_update = Game.draw_story_update
def _empire_draw_story_update(self):
    _ensure_empire_state(self)
    if self.empire_mode and not self.empire_won:
        pyxel.cls(C0)
        key = self.current_country()["key"]
        pyxel.text(82, 22, "ASCENSION PROTOCOL", C14)
        pyxel.text(52, 38, self.current_country()["name"] + " HAS BEEN UPGRADED INTO", C7)
        pyxel.text(70, 52, _empire_name(self), C10)
        draw_country_outline(key, 108, 66, scale=3, color=C14, fill=False)
        draw_flag(key, 146, 94, 30, 18, pyxel.frame_count * 0.25)
        pyxel.text(54, 128, "TEN MORE LEVELS. ONE HEALTH BAR.", C8)
        pyxel.text(52, 142, "THE FLAG IS YOUR ARMOR NOW.", C7)
        pyxel.text(90, 170, "PRESS ENTER TO CONTINUE", C10)
        return
    _old_empire_draw_story_update(self)
Game.draw_story_update = _empire_draw_story_update
_old_empire_update_story = Game.update_story_update
def _empire_update_story_update(self):
    _ensure_empire_state(self)
    if self.empire_mode and not self.empire_won:
        self.intro_timer += 1
        if pyxel.btnp(pyxel.KEY_RETURN):
            self.state = "playing"
            stop_story_music()
        return
    _old_empire_update_story(self)
Game.update_story_update = _empire_update_story_update
_old_empire_draw_victory = Game.draw_victory
def _empire_draw_victory(self):
    _ensure_empire_state(self)
    if self.empire_mode and self.empire_won:
        pyxel.cls(C0)
        key = self.current_country()["key"]
        r = 20 + (pyxel.frame_count // 4) % 28
        pyxel.circb(160, 86, r, C10)
        pyxel.circb(160, 86, r + 10, C14)
        draw_country_outline(key, 108, 34, scale=4, color=C14, fill=False)
        draw_flag(key, 142, 74, 36, 22, pyxel.frame_count * 0.25)
        pyxel.text(100, 124, "EMPIRE COMPLETE", C14)
        pyxel.text(48, 140, _empire_name(self) + " ENDURES", C7)
        pyxel.text(98, 154, "FINAL SCORE " + str(self.player.score), C10)
        pyxel.text(78, 172, "PRESS ENTER FOR MENU", C7)
        return
    _old_empire_draw_victory(self)
Game.draw_victory = _empire_draw_victory
_old_empire_player_draw = Player.draw
def _empire_player_draw(self, cam_x):
    _old_empire_player_draw(self, cam_x)
    game = globals().get("_active_secret_game")
    if game is None or not getattr(game, "empire_mode", False):
        return
    sx = int(self.x - cam_x)
    sy = int(self.y)
    key = self.country["key"]
    if self.hurt_flash > 0 and (pyxel.frame_count // 2) % 2 == 0:
        return
    draw_flag(key, sx + 1, sy + 6, 14, 8, pyxel.frame_count * 0.18)
    draw_flag(key, sx - 9 if self.facing == 1 else sx + 11, sy + 11, 13, 8, pyxel.frame_count * 0.24)
    pyxel.circb(sx + 8, sy + 11, 15 + ((pyxel.frame_count // 5) % 2), C14)
    pyxel.text(sx + 3, sy - 7, "I", C10)
Player.draw = _empire_player_draw
def _secret_draw_hud(self):
    _old_secret_draw_hud(self)
    _ensure_empire_state(self)
    if self.empire_mode:
        pyxel.rect(86, 14, 148, 10, C0)
        pyxel.rectb(86, 14, 148, 10, C10)
        pyxel.text(91, 17, _empire_name(self)[:23] + "  1HP", C10)
Game.draw_hud = _secret_draw_hud
_old_empire_reset_run2 = Game.reset_run
def _empire_reset_run(self):
    _old_empire_reset_run2(self)
    _ensure_empire_state(self)
    self.empire_mode = False
    self.empire_won = False
    self.ooo_route_unlocked = False
    self.secret_dev_inside = False
Game.reset_run = _empire_reset_run
MAJOR_EMPIRE_KEYS = [
    "usa", "china", "russia", "germany", "uk",
    "france", "japan", "brazil", "turkiye", "italy",
]
MAJOR_EMPIRE_DATA = {
    "usa": {
        "empire": "AMERICAN EMPIRE", "power": "CARRIER GROUPS", "growth": "COAST TO COAST COMMAND",
        "x": 62, "y": 44, "scale": 4,
    },
    "china": {
        "empire": "CELESTIAL DYNASTY", "power": "DRAGON FLEETS", "growth": "MEGACITY SHIELD GRID",
        "x": 62, "y": 44, "scale": 4,
    },
    "russia": {
        "empire": "EASTERN TSARDOM", "power": "ARCTIC ARMOR COLUMNS", "growth": "ENDLESS FRONTIER",
        "x": 50, "y": 50, "scale": 4,
    },
    "germany": {
        "empire": "IRON KAISERREICH", "power": "TITAN FACTORIES", "growth": "INDUSTRIAL HEARTLAND",
        "x": 106, "y": 38, "scale": 5,
    },
    "uk": {
        "empire": "BRITISH EMPIRE", "power": "ROYAL AIR ARMADA", "growth": "SEA LANES RECLAIMED",
        "x": 106, "y": 38, "scale": 5,
    },
    "france": {
        "empire": "NAPOLEONIC EMPIRE", "power": "GRAND BATTERIES", "growth": "TRICOLOR LEGIONS",
        "x": 104, "y": 38, "scale": 5,
    },
    "japan": {
        "empire": "RISING SUN SHOGUNATE", "power": "SKY FORTRESS WINGS", "growth": "ISLAND CHAIN AWAKENS",
        "x": 112, "y": 36, "scale": 5,
    },
    "brazil": {
        "empire": "BRAZILIAN EMPIRE", "power": "JUNGLE MECH BRIGADES", "growth": "AMAZONIAN BASTION",
        "x": 96, "y": 36, "scale": 5,
    },
    "turkiye": {
        "empire": "ANATOLIAN SULTANATE", "power": "BOSPHORUS CANNONS", "growth": "TWIN-CONTINENT COMMAND",
        "x": 88, "y": 46, "scale": 5,
    },
    "italy": {
        "empire": "ROMAN EMPIRE", "power": "GOLDEN LEGIONS", "growth": "ROADS OF ROME RESTORED",
        "x": 106, "y": 34, "scale": 5,
    },
}
def _empire_name(game):
    key = game.current_country()["key"]
    if key in MAJOR_EMPIRE_DATA:
        return MAJOR_EMPIRE_DATA[key]["empire"]
    return EMPIRE_NAMES.get(key, game.current_country()["name"] + " EMPIRE")
def _empire_data(game):
    key = game.current_country()["key"]
    return MAJOR_EMPIRE_DATA.get(key, {
        "empire": _empire_name(game),
        "power": "ELITE NATIONAL GUARD",
        "growth": "FORTIFIED HOMELAND",
        "x": 96,
        "y": 40,
        "scale": 4,
    })
def _draw_starburst(cx, cy, r, col1, col2):
    for i in range(16):
        a = i * math.tau / 16 + pyxel.frame_count * 0.03
        r2 = r if i % 2 == 0 else max(4, r // 2)
        x = cx + int(math.cos(a) * r2)
        y = cy + int(math.sin(a) * r2)
        pyxel.line(cx, cy, x, y, col1 if i % 2 == 0 else col2)
def _draw_military_power_meter(x, y, value, label):
    value = clamp(value, 0, 100)
    pyxel.rect(x, y, 126, 10, C0)
    pyxel.rectb(x, y, 126, 10, C7)
    pyxel.rect(x + 1, y + 1, int(124 * value / 100), 8, C10)
    for tick in range(0, 101, 25):
        tx = x + 1 + int(124 * tick / 100)
        pyxel.line(tx, y, tx, y + 10, C5)
    pyxel.text(x, y - 8, label + " " + str(int(value)) + "%", C14 if value > 70 else C7)
def _draw_tiny_tanks(x, y, count, col):
    for i in range(count):
        tx = x + i * 16
        bob = (pyxel.frame_count + i * 5) % 10 < 5
        pyxel.rect(tx, y + (1 if bob else 0), 11, 4, col)
        pyxel.rect(tx + 3, y - 3 + (1 if bob else 0), 5, 3, col)
        pyxel.line(tx + 7, y - 2 + (1 if bob else 0), tx + 14, y - 4 + (1 if bob else 0), C7)
        pyxel.pset(tx + 2, y + 5, C0)
        pyxel.pset(tx + 8, y + 5, C0)
def _draw_tiny_jets(x, y, count, col):
    for i in range(count):
        jx = x + i * 24 + (pyxel.frame_count // 2 + i * 9) % 18
        jy = y + (i % 3) * 7
        pyxel.tri(jx, jy, jx - 8, jy + 3, jx - 8, jy - 3, col)
        pyxel.line(jx - 6, jy, jx - 16, jy + 4, C6)
def _draw_empire_commander_portrait(x, y, key, progress):
    glow_r = 15 + int(8 * progress) + ((pyxel.frame_count // 5) % 2)
    pyxel.circb(x + 12, y + 18, glow_r, C10)
    pyxel.circb(x + 12, y + 18, glow_r + 5, C14)
    pyxel.line(x + 25, y - 6, x + 25, y + 32, C7)
    draw_flag(key, x + 27, y - 5, 38, 22, pyxel.frame_count * 0.20)
    pyxel.tri(x + 8, y + 12, x - 12, y + 36, x + 9, y + 34, C8)
    pyxel.tri(x + 16, y + 12, x + 36, y + 36, x + 15, y + 34, C8)
    pyxel.rect(x + 5, y + 12, 15, 20, C10)
    pyxel.rectb(x + 5, y + 12, 15, 20, C7)
    pyxel.rect(x + 7, y + 15, 11, 5, C14)
    pyxel.rect(x + 4, y + 2, 17, 12, C10)
    pyxel.rect(x + 6, y + 5, 13, 5, C7)
    pyxel.pset(x + 8, y + 7, C0)
    pyxel.pset(x + 16, y + 7, C0)
    pyxel.tri(x + 7, y + 2, x + 12, y - 7, x + 17, y + 2, C14)
    pyxel.rect(x + 5, y + 32, 5, 5, C10)
    pyxel.rect(x + 15, y + 32, 5, 5, C10)
    pyxel.line(x + 18, y + 17, x + 25, y + 10, C10)
    pyxel.line(x + 19, y + 17, x + 26, y + 10, C7)
def _start_empire_ascension(game):
    _ensure_empire_state(game)
    game.empire_mode = False
    game.empire_won = False
    game.empire_anim_timer = 0
    game.empire_anim_done = False
    game.empire_old_score = game.player.score
    game.state = "empire_ascension"
    game.intro_timer = 0
    game.effects.clear(); game.particles.clear(); game.texts.clear()
    play_story_music()
def _cinematic_empire_next_level(self):
    _ensure_empire_state(self)
    if not self.empire_mode:
        if self.level >= CAMPAIGN_LEVELS:
            _start_empire_ascension(self)
            return
        return _old_empire_next_level(self)
    return _empire_next_level(self)
Game.next_level = _cinematic_empire_next_level
def _update_empire_ascension(self):
    _ensure_empire_state(self)
    if not hasattr(self, "empire_anim_timer"):
        self.empire_anim_timer = 0
    self.empire_anim_timer += 1
    if pyxel.btnp(pyxel.KEY_RETURN) or self.empire_anim_timer > 420:
        _enter_empire_campaign(self, from_dev=False)
        self.state = "playing"
        stop_story_music()
def _draw_empire_ascension(self):
    _ensure_empire_state(self)
    t = getattr(self, "empire_anim_timer", 0)
    key = self.current_country()["key"]
    data = _empire_data(self)
    progress = clamp(t / 360.0, 0, 1)
    territory = clamp((t - 20) / 180.0, 0, 1)
    military = clamp((t - 90) / 210.0, 0, 1)
    commander = clamp((t - 170) / 130.0, 0, 1)
    pyxel.cls(C0)
    for i in range(0, SCREEN_W, 16):
        pyxel.line(i, 0, i - 60, SCREEN_H, C1)
    pyxel.rect(12, 10, 296, 180, C0)
    pyxel.rectb(12, 10, 296, 180, C5)
    pyxel.line(18, 24, 302, 24, C6)
    pyxel.text(28, 16, "EMPIRE ASCENSION SEQUENCE", C14)
    pyxel.text(196, 16, "TOP-10 POWER DOCTRINE" if key in MAJOR_EMPIRE_KEYS else "NATIONAL ASCENSION", C10)
    map_x = data.get("x", 90)
    map_y = data.get("y", 38)
    scale = data.get("scale", 4)
    _draw_starburst(95, 88, int(12 + 42 * territory), C5, C10)
    draw_country_outline(key, map_x, map_y, scale=scale, color=C6, fill=False)
    if territory > 0:
        for ring in range(1, 4):
            off = int(ring * 5 * territory)
            col = C10 if ring == 1 else C14 if ring == 2 else C7
            draw_country_outline(key, map_x - off, map_y - off // 2, scale=scale, color=col, fill=False)
            draw_country_outline(key, map_x + off, map_y + off // 2, scale=scale, color=col, fill=False)
    if territory > 0.45:
        pyxel.text(26, 138, "TERRITORY GROWTH:", C7)
        pyxel.text(126, 138, data["growth"][:27], C10)
    _draw_military_power_meter(166, 47, military * 100, "MILITARY POWER")
    _draw_tiny_tanks(169, 70, int(1 + military * 6), C10)
    _draw_tiny_jets(166, 92, int(1 + military * 4), C12)
    if military > 0.40:
        pyxel.text(166, 122, "NEW DOCTRINE:", C7)
        pyxel.text(166, 134, data["power"][:23], C14)
    if commander > 0:
        _draw_empire_commander_portrait(214, 143, key, commander)
        pyxel.text(32, 154, self.current_country()["name"] + " RISES INTO", C7)
        pyxel.text(32, 166, data["empire"][:31], C10)
    else:
        pyxel.rect(229, 155, 14, 20, C12)
        pyxel.rect(227, 145, 18, 12, C12)
        pyxel.rect(230, 149, 12, 4, C14)
        pyxel.text(202, 178, "COMMANDER UPGRADE PENDING", C6)
    if t > 310:
        pyxel.rect(20, 178, 280, 8, C10 if (pyxel.frame_count // 5) % 2 else C14)
        pyxel.text(62, 180, "ASCENSION COMPLETE - PRESS ENTER OR WAIT", C0)
def _empire_controls_update(self):
    if pyxel.btnp(pyxel.KEY_P) and self.state == "playing":
        self.state = "paused"
        self.pause_index = 0
        return
    if self.state == "empire_ascension":
        self.update_empire_ascension()
        return
    return _old_empire_global_update(self)
_old_empire_global_update = Game.update
Game.update_empire_ascension = _update_empire_ascension
Game.update = _empire_controls_update
def _empire_controls_draw(self):
    if self.state == "empire_ascension":
        self.draw_empire_ascension()
        return
    return _old_empire_global_draw(self)
_old_empire_global_draw = Game.draw
Game.draw_empire_ascension = _draw_empire_ascension
Game.draw = _empire_controls_draw
def _gold_empire_player_draw(self, cam_x):
    game = globals().get("_active_secret_game")
    if game is None or not getattr(game, "empire_mode", False):
        return _old_empire_player_draw(self, cam_x)
    sx = int(self.x - cam_x)
    sy = int(self.y)
    if self.hurt_flash > 0 and (pyxel.frame_count // 2) % 2 == 0:
        return
    key = self.country["key"]
    if self.invuln > 0 and (pyxel.frame_count // 4) % 2 == 0:
        pyxel.circb(sx + 8, sy + 11, 17, C10)
        pyxel.circb(sx + 8, sy + 11, 14, C14)
    pole_x = sx + 22 if self.facing == 1 else sx - 8
    pyxel.line(pole_x, sy - 9, pole_x, sy + 28, C7)
    flag_x = pole_x + 1 if self.facing == 1 else pole_x - 33
    draw_flag(key, flag_x, sy - 8, 32, 19, pyxel.frame_count * 0.20)
    pyxel.tri(sx + 5, sy + 8, sx - 8, sy + 25, sx + 6, sy + 23, C8)
    pyxel.tri(sx + 12, sy + 8, sx + 25, sy + 25, sx + 11, sy + 23, C8)
    pyxel.line(sx + 2, sy + 12, sx - 5, sy + 24, C10)
    pyxel.line(sx + 15, sy + 12, sx + 22, sy + 24, C10)
    pyxel.rect(sx + 4, sy + 9, 9, 14, C10)
    pyxel.rect(sx + 6, sy + 11, 5, 10, C14)
    pyxel.rectb(sx + 4, sy + 9, 9, 14, C7)
    pyxel.rect(sx + 1, sy + 10, 4, 4, C10)
    pyxel.rect(sx + 12, sy + 10, 4, 4, C10)
    pyxel.line(sx + 14, sy + 13, pole_x, sy + 6, C10)
    pyxel.line(sx + 15, sy + 13, pole_x, sy + 6, C7)
    pyxel.rect(sx + 3, sy + 1, 11, 8, C10)
    pyxel.rect(sx + 5, sy + 4, 7, 3, C7)
    pyxel.pset(sx + 6, sy + 5, C0)
    pyxel.pset(sx + 11, sy + 5, C0)
    pyxel.tri(sx + 4, sy + 1, sx + 8, sy - 6, sx + 12, sy + 1, C14)
    pyxel.pset(sx + 8, sy - 6, C7)
    step = 1 if self.on_ground and abs(self.vx) > 0.1 and (self.walk_frame // 5) % 2 == 0 else -1
    pyxel.rect(sx + 3 + step, sy + 23, 5, 3, C0)
    pyxel.rect(sx + 10 - step, sy + 23, 5, 3, C0)
    if self.shoot_anim > 0:
        mx = sx + 20 if self.facing == 1 else sx - 3
        my = sy + 9
        pyxel.circ(mx, my, 4, C10)
        pyxel.circ(mx, my, 2, C14)
        pyxel.pset(mx, my, C7)
Player.draw = _gold_empire_player_draw
def _draw_dev_panel(game):
    _ensure_creative_state(game)
    _ensure_empire_state(game)
    if not game.dev_mode:
        return
    rows = _dev_rows(game)
    pyxel.rect(204, 12, 112, 18, C0)
    pyxel.rectb(204, 12, 112, 18, C14)
    pyxel.text(209, 17, "BUNKER DEV TERMINAL", C14)
    if not game.dev_panel_open:
        pyxel.text(214, 32, "SPACE OPENS PANEL", C6)
        return
    x, y, w, h = 22, 12, 276, 176
    pulse = (pyxel.frame_count // 6) % 2
    neon = C14 if pulse else C12
    pyxel.rect(x, y, w, h, C0)
    pyxel.rectb(x, y, w, h, neon)
    pyxel.rect(x + 4, y + 4, w - 8, 13, C1)
    pyxel.text(x + 9, y + 8, "UNDERGROUND COMMAND / PULL-SLIDER DEV RIG", C14)
    inst_x = x + 8
    inst_y = y + 25
    pyxel.rect(inst_x, inst_y, 86, 70, C1)
    pyxel.rectb(inst_x, inst_y, 86, 70, C5)
    pyxel.text(inst_x + 5, inst_y + 5, "CONTROLS", C10)
    pyxel.text(inst_x + 5, inst_y + 17, "MOUSE: DRAG", C7)
    pyxel.text(inst_x + 5, inst_y + 27, "UP/DOWN: ROW", C7)
    pyxel.text(inst_x + 5, inst_y + 37, "LEFT/RIGHT: SET", C7)
    pyxel.text(inst_x + 5, inst_y + 47, "SHIFT: FAST", C7)
    pyxel.text(inst_x + 5, inst_y + 57, "SPACE: HIDE", C7)
    pyxel.rect(inst_x, inst_y + 78, 86, 50, C1)
    pyxel.rectb(inst_x, inst_y + 78, 86, 50, C5)
    pyxel.text(inst_x + 5, inst_y + 83, "HOTKEYS", C10)
    pyxel.text(inst_x + 5, inst_y + 95, "S SUPPLY DROP", C7)
    pyxel.text(inst_x + 5, inst_y + 105, "B SPAWN BOSS", C7)
    pyxel.text(inst_x + 5, inst_y + 115, "C CLEAR FIELD", C7)
    pyxel.text(inst_x + 5, inst_y + 125, "R RESET RUN", C7)
    bay_x = x + 102
    bay_y = y + 25
    pyxel.rect(bay_x, bay_y, 162, 136, C1)
    pyxel.rectb(bay_x, bay_y, 162, 136, C12)
    pyxel.text(bay_x + 8, bay_y + 6, "LIVE RUN VARIABLES", C14)
    start = max(0, min(game.dev_cursor - 4, max(0, len(rows) - 9)))
    bar_x = bay_x + 54
    bar_w = 88
    for i in range(start, min(start + 9, len(rows))):
        label, value, lo, hi, step, _apply = rows[i]
        row_y = bay_y + 22 + (i - start) * 12
        selected = i == game.dev_cursor
        if selected:
            pyxel.rect(bay_x + 5, row_y - 3, 151, 10, C0)
            pyxel.rectb(bay_x + 5, row_y - 3, 151, 10, C14)
        label2 = "PICK LV" if label == "LEVEL" else label
        pyxel.text(bay_x + 10, row_y, label2, C14 if selected else C7)
        pyxel.text(bay_x + 43, row_y, str(int(value)), C10 if selected else C6)
        pyxel.rect(bar_x, row_y + 2, bar_w, 3, C5)
        ratio = 0 if hi == lo else (value - lo) / (hi - lo)
        fill = int(bar_w * clamp(ratio, 0, 1))
        fill_col = C10
        if label == "EMPIRE" and value:
            fill_col = C14
        pyxel.rect(bar_x, row_y + 2, fill, 3, fill_col)
        knob_x = bar_x + fill
        pyxel.rect(knob_x - 3, row_y - 2, 7, 9, C14 if selected else C7)
        pyxel.rect(knob_x - 1, row_y, 3, 5, C0)
    core_x = x + w - 20
    pyxel.rect(core_x, y + 28, 10, 122, C1)
    pyxel.rectb(core_x, y + 28, 10, 122, C12)
    for yy in range(y + 34, y + 146, 9):
        pyxel.rect(core_x + 3, yy, 4, 4, C3 if (yy + pyxel.frame_count) % 18 < 9 else C11)
    pyxel.text(x + 102, y + h - 16, "BUNKER LINK: ACTIVE    OOO ROUTE: SILENT", C6)
def _dev_update_panel(game):
    _ensure_creative_state(game)
    _ensure_empire_state(game)
    rows = _dev_rows(game)
    if pyxel.btnp(pyxel.KEY_UP):
        game.dev_cursor = (game.dev_cursor - 1) % len(rows); sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_DOWN):
        game.dev_cursor = (game.dev_cursor + 1) % len(rows); sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_LEFT):
        _apply_dev_delta(game, -1)
    if pyxel.btnp(pyxel.KEY_RIGHT):
        _apply_dev_delta(game, 1)
    mx, my = pyxel.mouse_x, pyxel.mouse_y
    mouse_btn = getattr(pyxel, "MOUSE_BUTTON_LEFT", 0)
    panel_x, panel_y = 22, 12
    bay_x, bay_y = panel_x + 102, panel_y + 25
    start = max(0, min(game.dev_cursor - 4, max(0, len(rows) - 9)))
    bar_x, bar_w = bay_x + 54, 88
    if pyxel.btnp(mouse_btn):
        game.dev_drag_row = -1
        for i in range(start, min(start + 9, len(rows))):
            row_y = bay_y + 22 + (i - start) * 12
            if bar_x - 9 <= mx <= bar_x + bar_w + 9 and row_y - 5 <= my <= row_y + 8:
                game.dev_cursor = i
                game.dev_drag_row = i
                _set_dev_row_from_mouse(game, i, mx, bar_x, bar_w)
                sfx(1, 4)
                break
    if pyxel.btn(mouse_btn) and getattr(game, "dev_drag_row", -1) >= 0:
        _set_dev_row_from_mouse(game, game.dev_drag_row, mx, bar_x, bar_w)
    if not pyxel.btn(mouse_btn):
        game.dev_drag_row = -1
    if pyxel.btnp(pyxel.KEY_SPACE):
        game.dev_panel_open = False
    if pyxel.btnp(pyxel.KEY_C):
        game.enemies.clear(); game.enemy_bullets.clear(); game.player_bullets.clear(); game.rockets.clear(); game.bombs.clear()
    if pyxel.btnp(pyxel.KEY_B):
        _dev_spawn_boss(game)
    if pyxel.btnp(pyxel.KEY_S):
        game.supply_drops.append(SupplyDrop(game.player.x + 90, -60, "field"))
    if pyxel.btnp(pyxel.KEY_R):
        game.reset_run(); game.dev_mode = True; game.dev_panel_open = True
TOP10_COUNTRY_KEYS = [
    "usa", "china", "russia", "germany", "uk",
    "france", "japan", "italy", "turkiye", "brazil",
]
REAL_EMPIRE_NAMES = {
    "usa": "AMERICAN EMPIRE",
    "china": "QING DYNASTY",
    "russia": "RUSSIAN EMPIRE",
    "germany": "IMPERIAL GERMANY",
    "uk": "BRITISH EMPIRE",
    "france": "FIRST FRENCH EMPIRE",
    "japan": "IMPERIAL JAPAN",
    "italy": "ROMAN EMPIRE",
    "turkiye": "OTTOMAN EMPIRE",
    "brazil": "EMPIRE OF BRAZIL",
}
WORST_ENEMY_MAP = {
    "usa": "russia",
    "china": "usa",
    "russia": "usa",
    "germany": "uk",
    "uk": "germany",
    "france": "germany",
    "japan": "usa",
    "italy": "uk",
    "turkiye": "russia",
    "brazil": "usa",
}
MAJOR_EMPIRE_DATA.update({
    "usa": {
        "empire": "AMERICAN EMPIRE", "power": "CARRIER STRIKE GROUPS", "growth": "CONTINENTAL COMMAND",
        "icon": "EAGLE STAR", "territory": "ATLANTIC-PACIFIC DOMINION"
    },
    "china": {
        "empire": "QING DYNASTY", "power": "DRAGON BANNER ARMIES", "growth": "MANDATE OF HEAVEN",
        "icon": "CELESTIAL DRAGON", "territory": "GRAND DYNASTIC REUNION"
    },
    "russia": {
        "empire": "RUSSIAN EMPIRE", "power": "IRON FRONT ARSENALS", "growth": "EURASIAN DEPTH",
        "icon": "DOUBLE EAGLE", "territory": "THE GREAT STEPPE THRONE"
    },
    "germany": {
        "empire": "IMPERIAL GERMANY", "power": "STEEL PANZER COLUMNS", "growth": "RHINE TO PRUSSIA",
        "icon": "IRON CROWN", "territory": "KAISERLICH COMMAND"
    },
    "uk": {
        "empire": "BRITISH EMPIRE", "power": "ROYAL ARMADA", "growth": "SEA-LANE SUPREMACY",
        "icon": "CROWN ANCHOR", "territory": "IMPERIAL COMMONWEALTH"
    },
    "france": {
        "empire": "FIRST FRENCH EMPIRE", "power": "GRANDE ARMEE", "growth": "TRICOLOUR ASCENT",
        "icon": "LAUREL EAGLE", "territory": "NAPOLEONIC FRONT"
    },
    "japan": {
        "empire": "IMPERIAL JAPAN", "power": "SHOGUNATE WAR FLEETS", "growth": "RISING SUN OFFENSIVE",
        "icon": "SUN CREST", "territory": "ISLAND FORTRESS EXPANSION"
    },
    "italy": {
        "empire": "ROMAN EMPIRE", "power": "GOLDEN LEGIONS", "growth": "VIA IMPERIUM",
        "icon": "LEGION EAGLE", "territory": "ROADS OF ROME RESTORED"
    },
    "turkiye": {
        "empire": "OTTOMAN EMPIRE", "power": "JANISSARY ENGINE", "growth": "CRESCENT DOMINION",
        "icon": "IMPERIAL CRESCENT", "territory": "GATE OF TWO CONTINENTS"
    },
    "brazil": {
        "empire": "EMPIRE OF BRAZIL", "power": "JUNGLE IMPERIAL BRIGADES", "growth": "AMAZONIAN SPHERE",
        "icon": "GREEN-ORBITAL STAR", "territory": "SOUTH ATLANTIC ASCENT"
    },
})
EMPIRE_NAMES.update(REAL_EMPIRE_NAMES)
_key_to_country = {c["key"]: dict(c) for c in COUNTRIES}
COUNTRIES[:] = [_key_to_country[k] for k in TOP10_COUNTRY_KEYS if k in _key_to_country]
SKIN_UPGRADES = [
    {"name": "COMMANDER PRIME", "trim": C7,  "plume": C8,  "accent": C14},
    {"name": "WARLORD REX",   "trim": C6,  "plume": C2,  "accent": C12},
    {"name": "IMPERATOR",     "trim": C10, "plume": C8,  "accent": C7},
    {"name": "NEON GENERAL",  "trim": C12, "plume": C15, "accent": C10},
    {"name": "IRON MARSHAL",  "trim": C7,  "plume": C1,  "accent": C8},
    {"name": "PHANTOM KING",  "trim": C7,  "plume": C2,  "accent": C15},
    {"name": "SUN EMPEROR",   "trim": C14, "plume": C8,  "accent": C10},
    {"name": "FROST TYRANT",  "trim": C12, "plume": C1,  "accent": C7},
]
for i, extra in enumerate(SKIN_UPGRADES):
    if i < len(Player.SKINS):
        Player.SKINS[i].update(extra)
EMPIRE_STORYLINES = {
    "usa": [
        ["WASHINGTON RATIFIES THE IMPERIAL ACT.", "COASTAL COMMANDS MERGE INTO ONE WAR MACHINE.", "THE STAR BANNER ADVANCES AGAIN."],
        ["THE ATLANTIC THEATER IS SECURED.", "NEW FORTRESS STATES FEED THE FRONT.", "YOUR STANDARD LEADS THE PUSH WEST."],
        ["AIR DOMINANCE HAS BEEN RESTORED.", "CARRIER GROUPS STRIKE BEYOND THE HORIZON.", "THE EMPIRE BUILDS THROUGH FIRE."],
        ["RESISTANCE HOLDS IN THE INDUSTRIAL BELT.", "CIVILIAN FACTORIES NOW FORGE ARMOR.", "YOUR NAME SPREADS THROUGH THE RANKS."],
        ["THE FINAL IMPERIAL CORRIDOR OPENS.", "ALLIED COMMAND FALLS INTO FORMATION.", "ONE LAST MARCH TO ABSOLUTE CONTROL."],
    ],
    "china": [
        ["THE QING BANNERS RISE OVER THE CAPITAL.", "THE MANDATE OF HEAVEN IS DECLARED RESTORED.", "THE DRAGON THRONE CALLS YOU FORWARD."],
        ["RIVER GARRISONS SWEAR NEW OATHS.", "SILK AND STEEL FEED THE DYNASTIC WAR CHEST.", "THE CELESTIAL FRONT EXPANDS."],
        ["THE SOUTHERN CITADELS OPEN THEIR GATES.", "THE GRAND CANAL BECOMES A WAR ARTERY.", "YOUR STANDARD BECOMES A DYNASTIC SYMBOL."],
        ["THE NORTHERN PASSES ARE SEALED.", "BANNER ARMIES REFORM INTO STRIKE LEGIONS.", "YOUR RULE HARDENS INTO ORDER."],
        ["THE IMPERIAL REUNIFICATION IS NEAR.", "THE DRAGON COURT DEMANDS TOTAL VICTORY.", "LEAD THE DYNASTY INTO ITS FINAL AGE."],
    ],
    "russia": [
        ["THE RUSSIAN EMPIRE IS PROCLAIMED AGAIN.", "THE FRONTIER GOVERNORATES RALLY TO THE CROWN.", "THE SNOWLINE MARCHES WITH YOU."],
        ["ARSENALS IN THE URALS THUNDER TO LIFE.", "RAILWAYS BECOME VEINS OF IRON.", "YOUR BANNER CROSSES THE STEPPE."],
        ["NORTHERN FLEETS SECURE THE FROZEN COAST.", "THE WINTER GUARDS SWELL IN NUMBER.", "THE EMPIRE PUSHES THROUGH ICE AND FIRE."],
        ["THE OLD EAGLE WATCHES FROM THE PALACES.", "BORDER FORTRESSES STAND UNBROKEN.", "THE LEGIONS WAIT FOR YOUR SIGNAL."],
        ["A FINAL STRIKE TOWARD THE HEARTLAND.", "THE TSARIST STANDARD SHINES OVER THE GUN LINE.", "END THE WAR IN ONE FROZEN HAMMER BLOW."],
    ],
    "germany": [
        ["THE KAISERLICH REGALIA RETURNS.", "PRUSSIAN DRILL MASTERS REFORM THE LEGIONS.", "IMPERIAL GERMANY AWAKENS."],
        ["THE RHINE FORTIFIES UNDER STEEL SKIES.", "PANZER COLUMNS ROLL FROM FORGE-CITIES.", "YOUR COMMAND TIGHTENS THE LINE."],
        ["THE OLD EAGLE FLIES ABOVE THE FACTORIES.", "FIELD GUNS SPEAK WITH MECHANICAL PRECISION.", "THE FRONT BECOMES A MACHINE."],
        ["NORTHERN SHIPYARDS LAUNCH IRON FLEETS.", "THE ARMY SWEEPS THROUGH DUST AND SMOKE.", "YOUR IMPERIAL STANDARD NEVER DIPS."],
        ["THE FINAL KAISER'S OFFENSIVE BEGINS.", "DISCIPLINE AND STEEL WILL DECIDE THIS AGE.", "LEAD THE CHARGE TO TOTAL BREAKTHROUGH."],
    ],
    "uk": [
        ["THE CROWN DECLARES IMPERIAL MOBILIZATION.", "SEA-LANES ARE REOPENED BY FORCE OF ARMS.", "THE BRITISH EMPIRE RISES ANEW."],
        ["DOCKYARDS IGNITE WITH MIDNIGHT INDUSTRY.", "ROYAL GUNNERS SECURE THE CHANNEL.", "YOUR FLAG BECOMES THE RALLY POINT."],
        ["COLONIAL VETERANS RETURN TO THE CORE.", "SKIES FILL WITH THE SOUND OF THE ROYAL AIR ARM.", "THE ISLES STAND UNBROKEN."],
        ["THE IMPERIAL FLEET FORMS A WALL OF STEEL.", "YOUR ADVANCE RESTORES THE WAR MAP.", "EVERY HARBOR TURNS TO A FORTRESS."],
        ["THE LAST NAVAL GATE OPENS.", "THE ENEMY COAST LIES IN RANGE.", "SEAL THE AGE WITH A CROWNED VICTORY."],
    ],
    "france": [
        ["THE FIRST FRENCH EMPIRE IS REKINDLED.", "EAGLES AND LAURELS RETURN TO THE BATTLEFIELD.", "PARIS SENDS YOU TO RESTORE GLORY."],
        ["THE GRANDE ARMEE RE-ENTERS THE MARCH.", "ARTILLERY TRAINS CARVE ROADS THROUGH FIRE.", "YOUR STANDARD IS SEEN FROM EVERY HILL."],
        ["THE TRICOLOUR HOLDS THE RIVER CITIES.", "VETERANS RALLY UNDER IMPERIAL DRUMS.", "THE FRONT MOVES TO YOUR CADENCE."],
        ["FORTRESSES OF STONE AND SMOKE ENDURE.", "THE EMPEROR'S ROAD GROWS LONGER EACH DAY.", "YOUR NAME ECHOES THROUGH THE RANKS."],
        ["THE FINAL IMPERIAL BATTERY IS IN PLACE.", "ONE DECISIVE STRIKE WILL SHATTER THE SIEGE.", "MARCH FRANCE INTO ITS NEW AGE."],
    ],
    "japan": [
        ["THE IMPERIAL SEAL IS REOPENED.", "THE RISING SUN OFFENSIVE BEGINS AT DAWN.", "YOUR COMMAND UNIT LEADS THE RESTORATION."],
        ["ISLAND FORTRESSES RECONNECT THROUGH FIRE.", "SKY SQUADRONS CUT OPEN THE CLOUDLINE.", "THE IMPERIAL STANDARD NEVER FALTERS."],
        ["SHIPYARDS RETURN TO FULL THROTTLE.", "MOUNTAIN GARRISONS STAND LIKE BLADES.", "YOUR ADVANCE REFORGES THE NATION."],
        ["THE WAR CABINET ORDERS TOTAL MOBILIZATION.", "THE HOMELAND BECOMES A LIVING BASTION.", "YOU CARRY THE SUN INTO BATTLE."],
        ["THE LAST HARBOR IS WITHIN REACH.", "A FINAL SURGE WILL COMPLETE THE RESTORATION.", "TAKE THE IMPERIAL AGE ACROSS THE SEA."],
    ],
    "italy": [
        ["THE ROMAN STANDARDS UNFURL ONCE MORE.", "THE EAGLE RETURNS TO THE FORUM OF WAR.", "THE ROAD TO EMPIRE OPENS UNDER YOUR FEET."],
        ["LEGIONS MARCH ALONG REBUILT STONE ROADS.", "THE OLD WALLS BECOME NEW FORTRESSES.", "YOUR BANNER MAKES HISTORY FEEL ALIVE."],
        ["THE MEDITERRANEAN HEARS THE DRUM OF OARS.", "AUXILIARY COHORTS REJOIN THE CORE.", "ROME HARDENS INTO A WAR ENGINE."],
        ["THE SENATE YIELDS TO THE LEGIONARY WILL.", "GOLDEN SHIELDS FILL THE HORIZON.", "YOUR COMMAND BECOMES IMPERIUM."],
        ["THE FINAL TRIUMPHAL ROAD LIES AHEAD.", "ONE LAST CAMPAIGN WILL COMPLETE THE RESTORATION.", "MARCH ROME INTO ETERNITY."],
    ],
    "turkiye": [
        ["THE OTTOMAN STANDARD IS RAISED AGAIN.", "THE CRESCENT THRONE RECLAIMS ITS VOICE.", "YOU LEAD THE NEW IMPERIAL HOST."],
        ["THE STRAITS ARE SEALED BY IRON CANNONS.", "JANISSARY FORGE-CAMPS FEED THE FRONT.", "YOUR CRESCENT BANNER COMMANDS FEAR."],
        ["ANATOLIAN GARRISONS BECOME IMPERIAL WALLS.", "THE CAPITAL THUNDERS WITH DRUMS OF WAR.", "YOUR ADVANCE BINDS THE CONTINENTS."],
        ["THE HORNS OF THE OLD EMPIRE RETURN.", "SKY BATTERIES GUARD THE MINARETS.", "THE FRONT BENDS AROUND YOUR WILL."],
        ["THE FINAL GATE OF THE EMPIRE OPENS.", "ONE LAST OFFENSIVE WILL SECURE THE THRONE.", "LEAD THE CRESCENT INTO ITS LAST TRIUMPH."],
    ],
    "brazil": [
        ["THE IMPERIAL ORDER OF BRAZIL IS REBORN.", "THE GREEN STANDARD RISES ABOVE THE BASIN.", "THE EMPIRE MARCHES UNDER YOUR SIGNAL."],
        ["JUNGLE BRIGADES MERGE WITH COASTAL GARRISONS.", "RIVER ROUTES BECOME LIFELINES OF WAR.", "YOUR ADVANCE ECHOES THROUGH THE CANOPY."],
        ["THE SOUTHERN CITIES ARM FOR TOTAL DEFENSE.", "AERIAL SCOUTS DOMINATE THE HUMID SKIES.", "THE IMPERIAL FRONT GROWS STRONGER."],
        ["THE AMAZONIAN BASTIONS LINK TOGETHER.", "THE ARMY MOVES LIKE A GREEN TIDE.", "YOUR FLAG TURNS FEAR INTO MOMENTUM."],
        ["THE LAST IMPERIAL PUSH BEGINS.", "A CONTINENTAL STRIKE WILL SEAL THE AGE.", "LEAD BRAZIL TO ITS FINAL ASCENT."],
    ],
}
def _better_empire_name(game):
    return REAL_EMPIRE_NAMES.get(game.current_country()["key"], game.current_country()["name"] + " EMPIRE")
_old_polish_update_playing = Game.update_playing
def _polish_update_playing(self):
    globals()["_active_secret_game"] = self
    _old_polish_update_playing(self)
    if getattr(self, "state", "") != "playing":
        return
    if getattr(self, "dev_mode", False) and getattr(self, "dev_panel_open", False):
        return
    if hasattr(self, "order_complete_timer") and self.order_complete_timer > 0:
        self.order_complete_timer -= 1
    center_x = self.player.x + PLAYER_W / 2
    if self.player.y > GROUND_Y - 2 and self.terrain.is_gap_at(center_x, self.difficulty_value()):
        self.player_hit(99)
        return
    if self.player.y > SCREEN_H - 6:
        self.player_hit(99)
        return
Game.update_playing = _polish_update_playing
_old_polish_reset_run = Game.reset_run
def _polish_reset_run(self):
    _old_polish_reset_run(self)
    self.order_complete_timer = 0
    self.order_complete_title = ""
    self.order_complete_reward = ""
Game.reset_run = _polish_reset_run
def _polish_complete_field_order(game):
    order = game.field_order
    if not order:
        return
    reward_coins = 12
    reward_score = 120 + int(order["goal"] * 4)
    game.player.score += reward_score
    game.coins_total += reward_coins
    game.player.rockets += 1
    game.player.hp = min(game.player.max_hp, game.player.hp + 1)
    game.order_complete_timer = 96
    game.order_complete_title = order.get("code", "FIELD ORDER")
    game.order_complete_reward = "+SUPPLY   +$%d   +RKT   +1HP" % reward_coins
    for _ in range(16):
        game.particles.append(Particle(game.player.x + 20 + random.uniform(-10, 10), 40 + random.uniform(-8, 8), random.uniform(-1.8, 1.8), random.uniform(-2.5, -0.3), random.choice([C10, C14, C7]), random.randint(20, 40), size=1, gravity=0.04))
    game.add_text(game.player.x + 18, 38, "ORDER COMPLETE", C14)
    game.supply_drops.append(SupplyDrop(game.player.x + 130, -60, "field"))
    game.field_order = None
    game.field_order_cooldown = 7 * FPS
globals()["_complete_field_order"] = _polish_complete_field_order
_old_polish_draw_hud = Game.draw_hud
def _polish_draw_hud(self):
    _old_polish_draw_hud(self)
    if getattr(self, "order_complete_timer", 0) <= 0:
        return
    t = self.order_complete_timer
    bx = 48
    by = 34 if t > 60 else 24 + (60 - t) // 2
    pulse = C14 if (pyxel.frame_count // 4) % 2 == 0 else C10
    pyxel.rect(bx, by, 224, 50, C0)
    pyxel.rectb(bx, by, 224, 50, pulse)
    pyxel.rect(bx + 4, by + 4, 216, 10, C1)
    for i in range(0, 216, 16):
        pyxel.line(bx + 4 + i, by + 4, bx + i, by + 14, C5)
    pyxel.text(bx + 58, by + 7, "FIELD ORDER COMPLETE", pulse)
    pyxel.text(bx + 16, by + 22, self.order_complete_title[:28], C7)
    pyxel.text(bx + 16, by + 34, self.order_complete_reward[:30], C10)
    pyxel.rect(bx + 176, by + 18, 28, 18, C1)
    pyxel.rectb(bx + 176, by + 18, 28, 18, C14)
    pyxel.line(bx + 190, by + 18, bx + 190, by + 36, C14)
    pyxel.line(bx + 176, by + 27, bx + 204, by + 27, C14)
Game.draw_hud = _polish_draw_hud
def _draw_skin_preview(x, y, skin_idx, country_key, empire=False, facing=1):
    skin = Player.SKINS[skin_idx % len(Player.SKINS)]
    body = C10 if empire else skin["body"]
    cape = C8 if empire else skin["cape"]
    visor = C14 if empire else skin["visor"]
    trim = skin.get("trim", C7)
    plume = C14 if empire else skin.get("plume", C8)
    accent = C7 if empire else skin.get("accent", C10)
    pole_x = x + 22 if facing == 1 else x - 10
    pyxel.line(pole_x, y - 14, pole_x, y + 33, C7)
    draw_flag(country_key, pole_x + (1 if facing == 1 else -25), y - 13, 24 if not empire else 28, 14 if not empire else 16, pyxel.frame_count * 0.12)
    pyxel.tri(x + 9, y + 10, x - 2, y + 31, x + 12, y + 26, cape)
    pyxel.tri(x + 18, y + 10, x + 29, y + 31, x + 16, y + 26, cape)
    pyxel.rect(x + 9, y + 12, 10, 14, body)
    pyxel.rect(x + 11, y + 14, 6, 10, trim)
    pyxel.rectb(x + 9, y + 12, 10, 14, C7)
    pyxel.rect(x + 7, y + 12, 3, 5, body)
    pyxel.rect(x + 19, y + 12, 3, 5, body)
    pyxel.line(x + 21, y + 16, pole_x, y + 6, trim)
    pyxel.line(x + 8, y + 16, x + 4, y + 24, trim)
    pyxel.rect(x + 10, y + 20, 8, 2, accent)
    pyxel.pset(x + 14, y + 18, visor)
    pyxel.rect(x + 8, y + 4, 12, 8, body)
    pyxel.rect(x + 10, y + 7, 8, 3, visor)
    pyxel.rectb(x + 8, y + 4, 12, 8, C7)
    pyxel.tri(x + 9, y + 4, x + 14, y - 4, x + 19, y + 4, plume)
    pyxel.pset(x + 14, y - 4, C7)
    pyxel.pset(x + 12, y + 8, C0)
    pyxel.pset(x + 16, y + 8, C0)
    pyxel.rect(x + 10, y + 26, 4, 7, C0)
    pyxel.rect(x + 15, y + 26, 4, 7, C0)
    pyxel.rect(x + 9, y + 32, 6, 2, trim)
    pyxel.rect(x + 14, y + 32, 6, 2, trim)
    if empire:
        pyxel.circb(x + 14, y + 18, 20, C10)
        pyxel.circb(x + 14, y + 18, 16, C14)
def _menu_update_v2(self):
    if pyxel.btnp(pyxel.KEY_LEFT):
        self.mode_index = (self.mode_index - 1) % len(self.mode_names)
    if pyxel.btnp(pyxel.KEY_RIGHT):
        self.mode_index = (self.mode_index + 1) % len(self.mode_names)
    if pyxel.btnp(pyxel.KEY_Q):
        self.country_index = (self.country_index - 1) % len(COUNTRIES)
        self.player.country_index = self.country_index
        self.save_data["selected_country"] = self.country_index
        save_save(self.save_data)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_E):
        self.country_index = (self.country_index + 1) % len(COUNTRIES)
        self.player.country_index = self.country_index
        self.save_data["selected_country"] = self.country_index
        save_save(self.save_data)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_A):
        self.skin_index = (self.skin_index - 1) % len(Player.SKINS)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_D):
        self.skin_index = (self.skin_index + 1) % len(Player.SKINS)
        sfx(1, 4)
    self.skin_index = clamp(self.skin_index, 0, len(Player.SKINS) - 1)
    self.country_index = clamp(self.country_index, 0, len(COUNTRIES) - 1)
    if pyxel.btnp(pyxel.KEY_U):
        if self.skin_index not in self.unlocked_skins:
            cost = SKIN_PRICES[self.skin_index]
            if self.coins_total >= cost:
                self.coins_total -= cost
                self.unlocked_skins.append(self.skin_index)
                self.unlocked_skins = sorted(set(self.unlocked_skins))
                self.save_data["coins_total"] = self.coins_total
                self.save_data["unlocked_skins"] = self.unlocked_skins
                save_save(self.save_data)
                sfx(0, 3)
    if self.is_multiplayer():
        if pyxel.btnp(pyxel.KEY_W):
            self.mp_mode_index = (self.mp_mode_index - 1) % len(self.mp_mode_names)
        if pyxel.btnp(pyxel.KEY_S):
            self.mp_mode_index = (self.mp_mode_index + 1) % len(self.mp_mode_names)
        if pyxel.btnp(pyxel.KEY_Z):
            self.mp_player_count = max(2, self.mp_player_count - 1)
        if pyxel.btnp(pyxel.KEY_X):
            self.mp_player_count = min(8, self.mp_player_count + 1)
    if pyxel.btnp(pyxel.KEY_RETURN):
        if self.skin_index in self.unlocked_skins:
            self.player.skin_index = self.skin_index
            self.player.country_index = self.country_index
            if self.is_multiplayer():
                self.reset_multiplayer_session()
            else:
                self.reset_run()
Game.update_menu = _menu_update_v2
def _menu_draw_v2(self):
    pyxel.cls(C0)
    self.draw_background()
    pyxel.rect(18, 12, 284, 176, C0)
    pyxel.rectb(18, 12, 284, 176, C7)
    pyxel.text(88, 22, "SUPREME LEADER SURVIVAL", C7)
    pyxel.text(108, 32, "COMMAND SELECTION", C14)
    pyxel.rect(32, 48, 256, 24, C1)
    pyxel.rectb(32, 48, 256, 24, C12)
    pyxel.text(40, 56, "COUNTRY", C7)
    draw_flag(self.current_country()["key"], 102, 54, 20, 12, pyxel.frame_count * 0.15)
    pyxel.text(130, 57, self.current_country()["name"], C10)
    pyxel.text(224, 57, str(self.country_index + 1) + "/" + str(len(COUNTRIES)), C6)
    pyxel.text(252, 57, "Q/E", C14)
    pyxel.rect(32, 78, 130, 96, C1)
    pyxel.rectb(32, 78, 130, 96, C12)
    _draw_skin_preview(81, 112, self.skin_index, self.current_country()["key"], empire=False)
    pyxel.text(42, 86, "SKIN HANGAR", C14)
    pyxel.text(42, 161, "A/D BROWSE", C6)
    pyxel.rect(170, 78, 118, 96, C1)
    pyxel.rectb(170, 78, 118, 96, C12)
    skin = Player.SKINS[self.skin_index]
    pyxel.text(178, 86, skin["name"][:18], skin.get("accent", C10))
    pyxel.text(178, 100, "BODY  " + str(skin["body"]), skin["body"])
    pyxel.text(178, 112, "CAPE  " + str(skin["cape"]), skin["cape"])
    pyxel.text(178, 124, "VISOR " + str(skin["visor"]), skin["visor"])
    if self.skin_index in self.unlocked_skins:
        pyxel.text(178, 140, "STATUS: UNLOCKED", C3)
        pyxel.text(178, 152, "READY FOR DEPLOYMENT", C7)
    else:
        cost = SKIN_PRICES[self.skin_index]
        pyxel.text(178, 140, "STATUS: LOCKED", C8)
        pyxel.text(178, 152, "U TO UNLOCK  " + str(cost) + "$", C10)
    pyxel.text(40, 178, "LEFT/RIGHT MODE  ENTER START", C6)
    x = 46
    for i, name in enumerate(self.mode_names):
        selected = i == self.mode_index
        w = 78 if len(name) < 10 else 96
        pyxel.rect(x, 182, w, 12, C12 if selected else C0)
        pyxel.rectb(x, 182, w, 12, C7)
        pyxel.text(x + 4, 185, name[:14], C0 if selected else C7)
        x += w + 6
Game.draw_menu = _menu_draw_v2
_old_story_update_draw_v2 = Game.draw_story_update
_old_story_update_update_v2 = Game.update_story_update
def _story_update_draw_v2(self):
    _ensure_empire_state(self)
    if self.empire_mode and not self.empire_won:
        pyxel.cls(C0)
        key = self.current_country()["key"]
        lines_bank = EMPIRE_STORYLINES.get(key, [["THE EMPIRE ADVANCES.", "YOUR WAR MACHINE GROWS.", "PRESS ONWARD."]])
        idx = (max(1, self.level) - 1) % len(lines_bank)
        lines = lines_bank[idx]
        title = _better_empire_name(self)
        pyxel.rect(18, 16, 284, 168, C0)
        pyxel.rectb(18, 16, 284, 168, C14)
        pyxel.text(94, 26, "EMPIRE STORY UPDATE", C14)
        pyxel.text(32, 44, title[:34], C10)
        draw_country_outline(key, 32, 60, scale=3, color=C7, fill=False)
        draw_flag(key, 118, 68, 30, 18, pyxel.frame_count * 0.18)
        pyxel.text(170, 58, "LEVEL " + str(self.level) + "/10", C6)
        pyxel.text(170, 70, "HP LIMIT: 1", C8)
        growth = min(100, 12 + self.level * 8)
        power = min(100, 18 + self.level * 7)
        pyxel.text(170, 92, "TERRITORY", C7)
        pyxel.rect(170, 100, 108, 5, C1)
        pyxel.rect(170, 100, int(108 * growth / 100), 5, C3)
        pyxel.text(170, 112, "MILITARY POWER", C7)
        pyxel.rect(170, 120, 108, 5, C1)
        pyxel.rect(170, 120, int(108 * power / 100), 5, C8)
        pyxel.rect(30, 120, 124, 46, C1)
        pyxel.rectb(30, 120, 124, 46, C5)
        pyxel.text(36, 128, lines[0][:28], C7)
        pyxel.text(36, 140, lines[1][:28], C7)
        pyxel.text(36, 152, lines[2][:28], C10)
        _draw_skin_preview(226, 132, self.skin_index, key, empire=True, facing=-1)
        pyxel.text(86, 174, "PRESS ENTER TO DEPLOY", C14)
        return
    return _old_story_update_draw_v2(self)
Game.draw_story_update = _story_update_draw_v2
def _story_update_update_v2(self):
    _ensure_empire_state(self)
    if self.empire_mode and not self.empire_won:
        self.intro_timer += 1
        if pyxel.btnp(pyxel.KEY_RETURN):
            self.state = "playing"
            stop_story_music()
        return
    return _old_story_update_update_v2(self)
Game.update_story_update = _story_update_update_v2
_old_boss_init_v2 = BossEnemy.__init__
def _boss_init_v2(self, x):
    _old_boss_init_v2(self, x)
    self.hp = 60
    self.max_hp = 60
BossEnemy.__init__ = _boss_init_v2
def _boss_draw_v2(self, cam_x):
    sx = int(self.x - cam_x)
    sy = int(self.y)
    game = globals().get("_active_secret_game")
    own_key = game.current_country()["key"] if game else "usa"
    foe_key = WORST_ENEMY_MAP.get(own_key, "russia")
    pulse = (pyxel.frame_count // 8) % 2
    steel = C7 if self.flash > 0 else C5
    glow = C10 if pulse else C8
    shadow = C0
    pyxel.rect(sx + 12, sy + 70, 52, 4, shadow)
    pyxel.rect(sx + 14, sy + 58, 15, 12, C1)
    pyxel.rect(sx + 47, sy + 58, 15, 12, C1)
    for tx in range(16, 28, 3):
        pyxel.rect(sx + tx, sy + 63, 2, 3, C6)
    for tx in range(49, 61, 3):
        pyxel.rect(sx + tx, sy + 63, 2, 3, C6)
    pyxel.rect(sx - 8, sy + 26, 18, 18, C1)
    pyxel.rect(sx + 66, sy + 26, 18, 18, C1)
    pyxel.line(sx + 2, sy + 35, sx - 14, sy + 42, steel)
    pyxel.line(sx + 74, sy + 35, sx + 90, sy + 42, steel)
    pyxel.rect(sx - 12, sy + 40, 6, 6, C8)
    pyxel.rect(sx + 84, sy + 40, 6, 6, C8)
    pyxel.rect(sx + 14, sy + 18, 48, 42, steel)
    pyxel.rect(sx + 10, sy + 24, 56, 32, C1)
    pyxel.rectb(sx + 14, sy + 18, 48, 42, C7)
    pyxel.rect(sx + 25, sy + 8, 26, 14, steel)
    pyxel.rect(sx + 27, sy + 10, 22, 10, C1)
    pyxel.rectb(sx + 25, sy + 8, 26, 14, C7)
    pyxel.rect(sx + 23, sy + 24, 30, 20, C0)
    pyxel.rectb(sx + 23, sy + 24, 30, 20, C7)
    draw_flag(foe_key, sx + 24, sy + 25, 28, 18, pyxel.frame_count * 0.2)
    pyxel.circ(sx + 38, sy + 46, 10, C1)
    pyxel.circ(sx + 38, sy + 46, 7, glow)
    pyxel.circ(sx + 38, sy + 46, 3 + pulse, C7)
    pyxel.pset(sx + 38, sy + 46, C0)
    pyxel.line(sx + 28, sy + 12, sx + 18, sy - 4, C6)
    pyxel.line(sx + 48, sy + 12, sx + 58, sy - 4, C6)
    pyxel.pset(sx + 18, sy - 4, C7)
    pyxel.pset(sx + 58, sy - 4, C7)
    pyxel.line(sx + 38, sy + 16, sx + 62, sy + 8, C7)
    pyxel.line(sx + 38, sy + 16, sx + 66, sy + 6, C7)
    pyxel.rect(sx + 62, sy + 5, 8, 5, C8)
    pyxel.tri(sx + 14, sy + 26, sx - 2, sy + 32, sx + 14, sy + 42, C12)
    pyxel.tri(sx + 62, sy + 26, sx + 78, sy + 32, sx + 62, sy + 42, C12)
    label = ("RIVAL WARLORD: " + foe_key.upper())[:29]
    pyxel.rect(60, 7, 200, 10, C0)
    pyxel.rectb(60, 7, 200, 10, C7)
    pyxel.rect(61, 8, int(198 * self.hp / max(1, self.max_hp)), 8, C8)
    pyxel.text(92, 1, label, C7)
BossEnemy.draw = _boss_draw_v2
def _player_draw_v2(self, cam_x):
    game = globals().get("_active_secret_game")
    empire = bool(game is not None and getattr(game, "empire_mode", False))
    sx = int(self.x - cam_x)
    sy = int(self.y)
    if self.hurt_flash > 0 and (pyxel.frame_count // 2) % 2 == 0:
        return
    skin = self.skin
    body = C10 if empire else skin["body"]
    cape = C8 if empire else skin["cape"]
    visor = C14 if empire else skin["visor"]
    trim = C14 if empire else skin.get("trim", C7)
    plume = C10 if empire else skin.get("plume", C8)
    accent = C7 if empire else skin.get("accent", C10)
    key = self.country["key"]
    shadow_y = sy + 24
    pyxel.rect(sx + 3, shadow_y, 12, 2, C0)
    if self.invuln > 0 and (pyxel.frame_count // 4) % 2 == 0:
        ring = C3 if not empire else C10
        pyxel.circb(sx + 8, sy + 11, 13 if not empire else 17, ring)
        pyxel.circb(sx + 8, sy + 11, 10 if not empire else 14, C7)
    pole_x = sx + 20 if self.facing == 1 else sx - 8
    pyxel.line(pole_x, sy - (8 if empire else 1), pole_x, sy + 25, C7)
    fw, fh = (12, 7) if not empire else (28, 16)
    fx = pole_x + 1 if self.facing == 1 else pole_x - fw - 1
    fy = sy + 1 if not empire else sy - 7
    draw_flag(key, fx, fy, fw, fh, pyxel.frame_count * 0.18)
    pyxel.tri(sx + 5, sy + 8, sx - (7 if empire else 4), sy + 24, sx + 6, sy + 22, cape)
    pyxel.tri(sx + 12, sy + 8, sx + (23 if empire else 20), sy + 24, sx + 11, sy + 22, cape)
    pyxel.rect(sx + 4, sy + 9, 9, 14, body)
    pyxel.rect(sx + 6, sy + 11, 5, 10, trim)
    pyxel.rectb(sx + 4, sy + 9, 9, 14, C7)
    pyxel.rect(sx + 1, sy + 11, 3, 5, body)
    pyxel.rect(sx + 13, sy + 11, 3, 5, body)
    pyxel.line(sx + 15, sy + 13, pole_x, sy + 8, trim)
    pyxel.rect(sx + 5, sy + 15, 7, 2, accent)
    pyxel.rect(sx + 5, sy + 20, 8, 2, accent)
    pyxel.pset(sx + 8, sy + 18, visor)
    pyxel.rect(sx + 3, sy + 1, 11, 8, body)
    pyxel.rect(sx + 5, sy + 4, 7, 3, visor)
    pyxel.rectb(sx + 3, sy + 1, 11, 8, C7)
    if empire:
        pyxel.tri(sx + 4, sy + 1, sx + 8, sy - 6, sx + 12, sy + 1, C14)
        pyxel.pset(sx + 8, sy - 6, C7)
    else:
        pyxel.tri(sx + 4, sy + 1, sx + 8, sy - 4, sx + 12, sy + 1, plume)
    pyxel.pset(sx + 6, sy + 5, C0)
    pyxel.pset(sx + 11, sy + 5, C0)
    step = 1 if self.on_ground and abs(self.vx) > 0.1 and (self.walk_frame // 5) % 2 == 0 else -1
    pyxel.rect(sx + 3 + step, sy + 23, 5, 3, C0)
    pyxel.rect(sx + 10 - step, sy + 23, 5, 3, C0)
    pyxel.rect(sx + 3 + step, sy + 22, 4, 2, trim)
    pyxel.rect(sx + 10 - step, sy + 22, 4, 2, trim)
    if self.shoot_anim > 0:
        mx = sx + 19 if self.facing == 1 else sx - 2
        my = sy + 11
        pyxel.circ(mx, my, 3 if not empire else 4, accent)
        pyxel.pset(mx, my, C7)
Player.draw = _player_draw_v2
globals()['_empire_name'] = _better_empire_name
BOSS_GATE_NORMAL_LEVELS = {3, 6, 9, 10}
BOSS_GATE_EMPIRE_LEVELS = {2, 4, 6, 8, 10}
EMPIRE_SPEED_BONUS = 0.32
EMPIRE_AIR_BONUS = 0.25
EMPIRE_JUMP_BONUS = 0.42
def _is_boss_gate_level(game):
    if getattr(game, "empire_mode", False):
        return game.level in BOSS_GATE_EMPIRE_LEVELS
    return game.level in BOSS_GATE_NORMAL_LEVELS
def _apply_empire_bonuses(game):
    if not getattr(game, "empire_mode", False):
        return
    game.player.speed = max(game.player.speed, 1.9 + EMPIRE_SPEED_BONUS)
    game.player.air_speed = max(game.player.air_speed, 1.6 + EMPIRE_AIR_BONUS)
    game.player.jump_power = min(game.player.jump_power, -5.5 - EMPIRE_JUMP_BONUS)
    game.player.max_hp = 1
    game.player.hp = min(game.player.hp, 1)
def _draw_empire_flag(key, x, y, w=24, h=14, wave=0, empire=True):
    """Stylized empire banners. These are fantasy-pixel homages, not exact modern flags."""
    if not empire:
        draw_flag(key, x, y, w, h, wave)
        return
    ox = int(math.sin(wave) * 1.1)
    rx = lambda v: x + v + ox
    pyxel.rectb(rx(0), y, w, h, C7)
    name = REAL_EMPIRE_NAMES.get(key, "")
    if key == "italy":
        pyxel.rect(rx(0), y, w, h, C8)
        pyxel.rect(rx(2), y + 2, w - 4, h - 4, C2)
        pyxel.circ(rx(w // 2), y + h // 2 - 1, 3, C10)
        pyxel.line(rx(w // 2 - 6), y + h // 2 + 3, rx(w // 2 + 6), y + h // 2 + 3, C10)
        pyxel.text(rx(3), y + h - 6, "SP", C10)
    elif key == "turkiye":
        pyxel.rect(rx(0), y, w, h, C8)
        pyxel.circ(rx(w // 2 - 3), y + h // 2, max(3, h // 4), C7)
        pyxel.circ(rx(w // 2 - 1), y + h // 2, max(3, h // 4), C8)
        pyxel.pset(rx(w // 2 + 6), y + h // 2, C7)
        pyxel.pset(rx(w // 2 + 7), y + h // 2 - 1, C7)
        pyxel.pset(rx(w // 2 + 7), y + h // 2 + 1, C7)
    elif key == "china":
        pyxel.rect(rx(0), y, w, h, C10)
        for i in range(4, w - 4, 4):
            pyxel.pset(rx(i), y + 4 + ((i + pyxel.frame_count // 5) % 5), C8)
            pyxel.pset(rx(i + 1), y + 5 + ((i + pyxel.frame_count // 5) % 4), C8)
        pyxel.circ(rx(w - 6), y + 5, 2, C8)
        pyxel.pset(rx(w - 5), y + 4, C7)
    elif key == "germany":
        pyxel.rect(rx(0), y, w, h // 3, C0)
        pyxel.rect(rx(0), y + h // 3, w, h // 3, C7)
        pyxel.rect(rx(0), y + 2 * h // 3, w, h - 2 * h // 3, C8)
        pyxel.rect(rx(w // 2 - 1), y + 2, 3, h - 4, C0)
        pyxel.rect(rx(w // 2 - 5), y + h // 2 - 1, 10, 3, C0)
        pyxel.pset(rx(w // 2), y + h // 2, C7)
    elif key == "russia":
        pyxel.rect(rx(0), y, w, h, C7)
        pyxel.rect(rx(0), y + h - 4, w, 4, C10)
        pyxel.circ(rx(w // 2 - 3), y + h // 2, 3, C0)
        pyxel.circ(rx(w // 2 + 3), y + h // 2, 3, C0)
        pyxel.line(rx(w // 2), y + 3, rx(w // 2), y + h - 3, C0)
    elif key == "uk":
        pyxel.rect(rx(0), y, w, h, C1)
        pyxel.line(rx(0), y, rx(w - 1), y + h - 1, C7)
        pyxel.line(rx(w - 1), y, rx(0), y + h - 1, C7)
        pyxel.rect(rx(w // 2 - 1), y, 3, h, C8)
        pyxel.rect(rx(0), y + h // 2 - 1, w, 3, C8)
        pyxel.tri(rx(w // 2 - 4), y + 3, rx(w // 2), y, rx(w // 2 + 4), y + 3, C10)
    elif key == "france":
        pyxel.rect(rx(0), y, w // 3, h, C1)
        pyxel.rect(rx(w // 3), y, w // 3, h, C7)
        pyxel.rect(rx(2 * w // 3), y, w - 2 * (w // 3), h, C8)
        pyxel.circ(rx(w // 2), y + h // 2, 3, C10)
        pyxel.line(rx(w // 2 - 5), y + h // 2 + 3, rx(w // 2 + 5), y + h // 2 + 3, C10)
    elif key == "japan":
        pyxel.rect(rx(0), y, w, h, C7)
        pyxel.circ(rx(w // 2), y + h // 2, 4, C8)
        for a in range(0, 360, 45):
            dx = int(math.cos(math.radians(a)) * (w // 2))
            dy = int(math.sin(math.radians(a)) * (h // 2))
            pyxel.line(rx(w // 2), y + h // 2, rx(w // 2) + dx, y + h // 2 + dy, C8)
    elif key == "brazil":
        pyxel.rect(rx(0), y, w, h, C3)
        pyxel.tri(rx(w // 2), y + 1, rx(2), y + h // 2, rx(w // 2), y + h - 2, C10)
        pyxel.tri(rx(w // 2), y + 1, rx(w - 2), y + h // 2, rx(w // 2), y + h - 2, C10)
        pyxel.circ(rx(w // 2), y + h // 2, 3, C12)
        pyxel.pset(rx(w // 2), y + h // 2, C7)
    elif key == "usa":
        for i in range(h):
            pyxel.line(rx(0), y + i, rx(w - 1), y + i, C8 if i % 2 == 0 else C7)
        pyxel.rect(rx(0), y, w // 2, h // 2 + 1, C1)
        pyxel.tri(rx(w // 2 + 2), y + 3, rx(w // 2 + 7), y + h // 2, rx(w // 2 + 2), y + h - 3, C10)
        pyxel.pset(rx(4), y + 3, C7)
        pyxel.pset(rx(7), y + 5, C7)
    else:
        draw_flag(key, x, y, w, h, wave)
_old_v3_reset_run = Game.reset_run
def _v3_reset_run(self):
    _old_v3_reset_run(self)
    self.level_boss_defeated = False
    self.level_boss_spawned = False
Game.reset_run = _v3_reset_run
_old_v3_next_level = Game.next_level
def _v3_next_level(self):
    self.level_boss_defeated = False
    self.level_boss_spawned = False
    return _old_v3_next_level(self)
Game.next_level = _v3_next_level
_old_v3_kill_enemy = Game.kill_enemy
def _v3_kill_enemy(self, enemy):
    was_boss = isinstance(enemy, BossEnemy)
    result = _old_v3_kill_enemy(self, enemy)
    if was_boss:
        self.level_boss_defeated = True
        self.level_boss_spawned = False
        self.add_text(enemy.x, enemy.y - 42, "GATE CLEARED", C14)
        self.distance = LEVEL_DISTANCE + 1
    return result
Game.kill_enemy = _v3_kill_enemy
_old_v3_update_playing = Game.update_playing
def _v3_update_playing(self):
    globals()["_active_secret_game"] = self
    _ensure_empire_state(self)
    _apply_empire_bonuses(self)
    if self.is_campaign() and _is_boss_gate_level(self) and not getattr(self, "level_boss_defeated", False):
        if self.distance >= LEVEL_DISTANCE - 35:
            self.distance = LEVEL_DISTANCE - 35
        if not any(isinstance(e, BossEnemy) and e.alive for e in self.enemies):
            if not getattr(self, "level_boss_spawned", False):
                self.level_boss_spawned = True
                self.enemies.append(BossEnemy(self.player.x + 210))
                self.add_text(self.player.x + 70, 34, "BOSS GATE LOCKED", C8)
                sfx(0, 7)
    _old_v3_update_playing(self)
    _apply_empire_bonuses(self)
    if getattr(self, "state", "") == "playing" and self.is_campaign() and _is_boss_gate_level(self) and not getattr(self, "level_boss_defeated", False):
        if self.distance >= LEVEL_DISTANCE - 6:
            self.distance = LEVEL_DISTANCE - 6
            if not any(isinstance(e, BossEnemy) and e.alive for e in self.enemies):
                self.enemies.append(BossEnemy(self.player.x + 210))
                self.level_boss_spawned = True
Game.update_playing = _v3_update_playing
_old_v3_enter_empire_campaign = _enter_empire_campaign
def _enter_empire_campaign(game, from_dev=False):
    _old_v3_enter_empire_campaign(game, from_dev)
    game.empire_powerup_notice = FPS * 4
    _apply_empire_bonuses(game)
globals()["_enter_empire_campaign"] = _enter_empire_campaign
def _draw_empire_ascension_v3(self):
    pyxel.cls(C0)
    _ensure_empire_state(self)
    key = self.current_country()["key"]
    data = _empire_data(self)
    t = getattr(self, "empire_ascension_timer", 0)
    p = clamp(t / 360.0, 0, 1)
    empire_name = _better_empire_name(self)
    for i in range(46):
        sx = (i * 37 + t * (1 + i % 3)) % SCREEN_W
        sy = (i * 19 + (i % 5) * 7) % SCREEN_H
        col = C1 if i % 4 else C5
        pyxel.pset(int(sx), int(sy), col)
    pyxel.rect(10, 10, 300, 180, C0)
    pyxel.rectb(10, 10, 300, 180, C14 if (t // 6) % 2 else C10)
    pyxel.rect(16, 16, 288, 14, C1)
    pyxel.text(58, 20, "IMPERIAL ASCENSION // TERRITORY COMMAND", C14)
    map_x, map_y = 28, 48
    pyxel.rect(map_x - 8, map_y - 8, 112, 88, C1)
    pyxel.rectb(map_x - 8, map_y - 8, 112, 88, C5)
    pyxel.text(map_x, map_y - 18, "TERRITORY GROWTH", C7)
    territory_r = int(12 + 44 * p)
    for r in range(territory_r, 4, -9):
        col = C3 if r % 18 else C11
        pyxel.circb(map_x + 50, map_y + 38, r, col)
    draw_country_outline(key, map_x + 14, map_y + 8, scale=2, color=C10 if t > 70 else C7, fill=t > 130)
    for i in range(7):
        ang = t * 0.03 + i * math.tau / 7
        px = map_x + 50 + int(math.cos(ang) * min(46, 12 + p * 48))
        py = map_y + 38 + int(math.sin(ang) * min(32, 8 + p * 36))
        pyxel.circ(px, py, 2, C10 if i % 2 else C8)
    cx, cy = 164, 100
    pyxel.rect(cx - 36, cy - 52, 72, 104, C1)
    pyxel.rectb(cx - 36, cy - 52, 72, 104, C12)
    pyxel.line(cx, cy - 52, cx, cy + 52, C5)
    for i in range(0, 104, 12):
        pyxel.line(cx - 36, cy - 52 + i, cx + 36, cy - 52 + i, C5)
    phase_gold = t > 120
    _draw_skin_preview(cx - 14, cy - 2, self.skin_index, key, empire=phase_gold)
    if t > 90:
        for i in range(10):
            yy = cy - 48 + (i * 11 + t * 2) % 96
            pyxel.line(cx - 31, yy, cx + 31, yy, C10 if i % 2 else C14)
    if t > 190:
        pyxel.text(cx - 30, cy + 58, "COMMANDER ASCENDED", C10)
    rx, ry = 222, 48
    pyxel.rect(rx - 8, ry - 8, 70, 88, C1)
    pyxel.rectb(rx - 8, ry - 8, 70, 88, C5)
    pyxel.text(rx - 2, ry - 18, "MILITARY POWER", C7)
    power = int(min(100, p * 125))
    pyxel.rect(rx, ry + 60, 52, 6, C0)
    pyxel.rect(rx, ry + 60, int(52 * power / 100), 6, C8)
    pyxel.rectb(rx, ry + 60, 52, 6, C7)
    pyxel.text(rx + 6, ry + 70, str(power) + "%", C10)
    for i in range(3):
        tx = rx + 2 + i * 18
        ty = ry + 36 + int(math.sin((t + i * 20) * 0.08) * 2)
        pyxel.rect(tx, ty, 13, 5, C6)
        pyxel.rect(tx + 4, ty - 3, 6, 3, C5)
        pyxel.line(tx + 8, ty - 2, tx + 16, ty - 4, C7)
        pyxel.pset(tx + 3, ty + 5, C0)
        pyxel.pset(tx + 10, ty + 5, C0)
    for i in range(2):
        jx = rx + 5 + ((t * 2 + i * 31) % 58)
        jy = ry + 10 + i * 12
        pyxel.tri(jx, jy, jx - 8, jy + 4, jx - 4, jy - 4, C12)
        pyxel.pset(jx - 1, jy, C7)
    pyxel.rect(25, 144, 270, 34, C1)
    pyxel.rectb(25, 144, 270, 34, C10)
    _draw_empire_flag(key, 34, 152, 36, 20, t * 0.15, empire=True)
    pyxel.text(78, 151, empire_name[:30], C10)
    pyxel.text(78, 162, "+ SPEED BOOST   + SMALL JUMP BOOST", C14)
    pyxel.text(78, 172, "ONE HEALTH BAR REMAINS", C8)
    if t < 90:
        pyxel.text(106, 36, "NATION UNDER COMMAND", C7)
    elif t < 180:
        pyxel.text(91, 36, "TERRITORY ABSORBED INTO EMPIRE", C10)
    elif t < 285:
        pyxel.text(102, 36, "MILITARY POWER SURGING", C8)
    else:
        pyxel.text(94, 36, "ASCENSION COMPLETE - DEPLOY", C14)
    if t > 315:
        pyxel.rect(64, 184, 192, 8, C14 if (t // 5) % 2 else C10)
        pyxel.text(92, 185, "PRESS ENTER OR WAIT", C0)
Game.draw_empire_ascension = _draw_empire_ascension_v3
_old_v3_story_update_draw = Game.draw_story_update
def _story_update_draw_v3(self):
    _ensure_empire_state(self)
    if self.empire_mode and not self.empire_won:
        pyxel.cls(C0)
        key = self.current_country()["key"]
        lines_bank = EMPIRE_STORYLINES.get(key, [["THE EMPIRE ADVANCES.", "YOUR WAR MACHINE GROWS.", "PRESS ONWARD."]])
        idx = (max(1, self.level) - 1) % len(lines_bank)
        lines = lines_bank[idx]
        title = _better_empire_name(self)
        pyxel.rect(18, 16, 284, 168, C0)
        pyxel.rectb(18, 16, 284, 168, C14)
        pyxel.text(92, 26, "EMPIRE STORY UPDATE", C14)
        pyxel.text(32, 44, title[:34], C10)
        draw_country_outline(key, 32, 60, scale=3, color=C7, fill=False)
        _draw_empire_flag(key, 118, 68, 34, 20, pyxel.frame_count * 0.18, empire=True)
        pyxel.text(170, 52, "LEVEL " + str(self.level) + "/10", C6)
        pyxel.text(170, 64, "HP LIMIT: 1", C8)
        pyxel.text(170, 76, "SPEED +" + str(int(EMPIRE_SPEED_BONUS * 100)), C14)
        pyxel.text(170, 88, "JUMP +" + str(int(EMPIRE_JUMP_BONUS * 100)), C14)
        growth = min(100, 12 + self.level * 8)
        power = min(100, 18 + self.level * 7)
        pyxel.text(170, 106, "TERRITORY", C7)
        pyxel.rect(170, 114, 108, 5, C1)
        pyxel.rect(170, 114, int(108 * growth / 100), 5, C3)
        pyxel.text(170, 126, "MILITARY POWER", C7)
        pyxel.rect(170, 134, 108, 5, C1)
        pyxel.rect(170, 134, int(108 * power / 100), 5, C8)
        pyxel.rect(30, 120, 124, 46, C1)
        pyxel.rectb(30, 120, 124, 46, C5)
        pyxel.text(36, 128, lines[0][:28], C7)
        pyxel.text(36, 140, lines[1][:28], C7)
        pyxel.text(36, 152, lines[2][:28], C10)
        _draw_skin_preview(226, 132, self.skin_index, key, empire=True, facing=-1)
        pyxel.text(86, 174, "PRESS ENTER TO DEPLOY", C14)
        return
    return _old_v3_story_update_draw(self)
Game.draw_story_update = _story_update_draw_v3
def _player_draw_v3(self, cam_x):
    game = globals().get("_active_secret_game")
    empire = bool(game is not None and getattr(game, "empire_mode", False))
    sx = int(self.x - cam_x)
    sy = int(self.y)
    if self.hurt_flash > 0 and (pyxel.frame_count // 2) % 2 == 0:
        return
    skin = self.skin
    body = C10 if empire else skin["body"]
    cape = C8 if empire else skin["cape"]
    visor = C14 if empire else skin["visor"]
    trim = C14 if empire else skin.get("trim", C7)
    plume = C10 if empire else skin.get("plume", C8)
    accent = C7 if empire else skin.get("accent", C10)
    key = self.country["key"]
    if self.dash_timer > 0:
        trail_cols = [C14, C10, C7] if empire else [accent, C12, C7]
        for i in range(1, 7):
            alpha_col = trail_cols[i % len(trail_cols)]
            ox = sx - self.dash_dir * i * 5
            oy = sy + int(math.sin((pyxel.frame_count + i * 3) * 0.6) * 2)
            pyxel.circb(ox + 8, oy + 11, max(2, 12 - i), alpha_col)
            pyxel.rect(ox + 4, oy + 9, 9, 12, alpha_col)
            pyxel.pset(ox + 8, oy + 5, C7)
            pole = ox + (21 if self.facing == 1 else -7)
            pyxel.line(pole, oy - 2, pole, oy + 22, C7)
            if empire:
                _draw_empire_flag(key, pole + (1 if self.facing == 1 else -18), oy - 3, 18, 10, pyxel.frame_count * 0.2 + i, True)
            else:
                draw_flag(key, pole + (1 if self.facing == 1 else -13), oy + 1, 12, 7, pyxel.frame_count * 0.2 + i)
    shadow_y = sy + 24
    pyxel.rect(sx + 3, shadow_y, 12, 2, C0)
    if self.invuln > 0 and (pyxel.frame_count // 4) % 2 == 0:
        ring = C3 if not empire else C10
        pyxel.circb(sx + 8, sy + 11, 13 if not empire else 18, ring)
        pyxel.circb(sx + 8, sy + 11, 10 if not empire else 14, C7)
    pole_x = sx + 20 if self.facing == 1 else sx - 8
    pyxel.line(pole_x, sy - (8 if empire else 1), pole_x, sy + 25, C7)
    fw, fh = (12, 7) if not empire else (30, 17)
    fx = pole_x + 1 if self.facing == 1 else pole_x - fw - 1
    fy = sy + 1 if not empire else sy - 7
    if empire:
        _draw_empire_flag(key, fx, fy, fw, fh, pyxel.frame_count * 0.18, empire=True)
    else:
        draw_flag(key, fx, fy, fw, fh, pyxel.frame_count * 0.18)
    pyxel.tri(sx + 5, sy + 8, sx - (7 if empire else 4), sy + 24, sx + 6, sy + 22, cape)
    pyxel.tri(sx + 12, sy + 8, sx + (23 if empire else 20), sy + 24, sx + 11, sy + 22, cape)
    if empire:
        pyxel.line(sx - 2, sy + 23, sx + 18, sy + 23, C10)
    pyxel.rect(sx + 4, sy + 9, 9, 14, body)
    pyxel.rect(sx + 6, sy + 11, 5, 10, trim)
    pyxel.rectb(sx + 4, sy + 9, 9, 14, C7)
    pyxel.rect(sx + 1, sy + 11, 3, 5, body)
    pyxel.rect(sx + 13, sy + 11, 3, 5, body)
    pyxel.line(sx + 15, sy + 13, pole_x, sy + 8, trim)
    pyxel.rect(sx + 5, sy + 15, 7, 2, accent)
    pyxel.rect(sx + 5, sy + 20, 8, 2, accent)
    pyxel.pset(sx + 8, sy + 18, visor)
    pyxel.rect(sx + 3, sy + 1, 11, 8, body)
    pyxel.rect(sx + 5, sy + 4, 7, 3, visor)
    pyxel.rectb(sx + 3, sy + 1, 11, 8, C7)
    if empire:
        pyxel.tri(sx + 4, sy + 1, sx + 8, sy - 7, sx + 12, sy + 1, C14)
        pyxel.pset(sx + 8, sy - 7, C7)
        pyxel.line(sx + 2, sy + 0, sx + 14, sy + 0, C10)
    else:
        pyxel.tri(sx + 4, sy + 1, sx + 8, sy - 4, sx + 12, sy + 1, plume)
    pyxel.pset(sx + 6, sy + 5, C0)
    pyxel.pset(sx + 11, sy + 5, C0)
    step = 1 if self.on_ground and abs(self.vx) > 0.1 and (self.walk_frame // 5) % 2 == 0 else -1
    pyxel.rect(sx + 3 + step, sy + 23, 5, 3, C0)
    pyxel.rect(sx + 10 - step, sy + 23, 5, 3, C0)
    pyxel.rect(sx + 3 + step, sy + 22, 4, 2, trim)
    pyxel.rect(sx + 10 - step, sy + 22, 4, 2, trim)
    if self.shoot_anim > 0:
        mx = sx + 19 if self.facing == 1 else sx - 2
        my = sy + 11
        pyxel.circ(mx, my, 3 if not empire else 4, accent)
        pyxel.pset(mx, my, C7)
Player.draw = _player_draw_v3
_old_boss_draw_v3_base = BossEnemy.draw
def _boss_draw_v3(self, cam_x):
    sx = int(self.x - cam_x)
    sy = int(self.y)
    game = globals().get("_active_secret_game")
    own_key = game.current_country()["key"] if game else "usa"
    foe_key = WORST_ENEMY_MAP.get(own_key, "russia")
    pulse = (pyxel.frame_count // 8) % 2
    steel = C7 if self.flash > 0 else C5
    glow = C10 if pulse else C8
    pyxel.rect(sx + 12, sy + 70, 52, 4, C0)
    pyxel.rect(sx + 14, sy + 58, 15, 12, C1)
    pyxel.rect(sx + 47, sy + 58, 15, 12, C1)
    for tx in range(16, 28, 3): pyxel.rect(sx + tx, sy + 63, 2, 3, C6)
    for tx in range(49, 61, 3): pyxel.rect(sx + tx, sy + 63, 2, 3, C6)
    pyxel.rect(sx - 8, sy + 26, 18, 18, C1)
    pyxel.rect(sx + 66, sy + 26, 18, 18, C1)
    pyxel.line(sx + 2, sy + 35, sx - 14, sy + 42, steel)
    pyxel.line(sx + 74, sy + 35, sx + 90, sy + 42, steel)
    pyxel.rect(sx - 12, sy + 40, 6, 6, C8)
    pyxel.rect(sx + 84, sy + 40, 6, 6, C8)
    pyxel.rect(sx + 14, sy + 18, 48, 42, steel)
    pyxel.rect(sx + 10, sy + 24, 56, 32, C1)
    pyxel.rectb(sx + 14, sy + 18, 48, 42, C7)
    pyxel.rect(sx + 25, sy + 8, 26, 14, steel)
    pyxel.rect(sx + 27, sy + 10, 22, 10, C1)
    pyxel.rectb(sx + 25, sy + 8, 26, 14, C7)
    pyxel.rect(sx + 20, sy + 23, 36, 23, C0)
    pyxel.rectb(sx + 20, sy + 23, 36, 23, C7)
    _draw_empire_flag(foe_key, sx + 22, sy + 25, 32, 18, pyxel.frame_count * 0.2, empire=True)
    pyxel.circ(sx + 38, sy + 49, 10, C1)
    pyxel.circ(sx + 38, sy + 49, 7, glow)
    pyxel.circ(sx + 38, sy + 49, 3 + pulse, C7)
    pyxel.pset(sx + 38, sy + 49, C0)
    pyxel.line(sx + 28, sy + 12, sx + 18, sy - 4, C6)
    pyxel.line(sx + 48, sy + 12, sx + 58, sy - 4, C6)
    pyxel.pset(sx + 18, sy - 4, C7); pyxel.pset(sx + 58, sy - 4, C7)
    pyxel.line(sx + 38, sy + 16, sx + 62, sy + 8, C7)
    pyxel.line(sx + 38, sy + 16, sx + 66, sy + 6, C7)
    pyxel.rect(sx + 62, sy + 5, 8, 5, C8)
    pyxel.tri(sx + 14, sy + 26, sx - 2, sy + 32, sx + 14, sy + 42, C12)
    pyxel.tri(sx + 62, sy + 26, sx + 78, sy + 32, sx + 62, sy + 42, C12)
    label = ("BOSS GATE: " + foe_key.upper())[:29]
    pyxel.rect(60, 7, 200, 10, C0)
    pyxel.rectb(60, 7, 200, 10, C7)
    pyxel.rect(61, 8, int(198 * self.hp / max(1, self.max_hp)), 8, C8)
    pyxel.text(100, 1, label, C7)
BossEnemy.draw = _boss_draw_v3
def _secret_hit_wall(game, bullet):
    return False
def _safe_bullet_xy(b):
    try:
        return float(getattr(b, 'x', 0)), float(getattr(b, 'y', 0))
    except Exception:
        return 0, 0
_old_draw_flag_v4 = draw_flag
def draw_flag(key, x, y, w=16, h=10, wave=0):
    ox = int(math.sin(wave) * 1.0)
    def rx(v): return int(x + v + ox)
    x = int(x); y = int(y); w = int(w); h = int(h)
    pyxel.rectb(rx(0), y, w, h, C7)
    if key == "usa":
        stripe_h = max(1, h // 7)
        for i in range(h):
            pyxel.line(rx(0), y+i, rx(w-1), y+i, C8 if (i//stripe_h)%2==0 else C7)
        pyxel.rect(rx(0), y, max(5, w//2), max(4, h//2+1), C1)
        for sx in range(2, max(4, w//2), 4):
            for sy in range(1, max(3, h//2), 3):
                pyxel.pset(rx(sx), y+sy, C7)
    elif key == "china":
        pyxel.rect(rx(0), y, w, h, C8)
        pyxel.tri(rx(3), y+1, rx(1), y+5, rx(6), y+4, C10)
        for px,py in [(9,2),(12,4),(11,7),(7,7)]:
            if px < w and py < h: pyxel.pset(rx(px), y+py, C10)
    elif key == "russia":
        pyxel.rect(rx(0), y, w, h//3, C7)
        pyxel.rect(rx(0), y+h//3, w, h//3, C12)
        pyxel.rect(rx(0), y+2*h//3, w, h-2*h//3, C8)
    elif key == "germany":
        pyxel.rect(rx(0), y, w, h//3, C0)
        pyxel.rect(rx(0), y+h//3, w, h//3, C8)
        pyxel.rect(rx(0), y+2*h//3, w, h-2*h//3, C10)
    elif key == "uk":
        pyxel.rect(rx(0), y, w, h, C1)
        pyxel.line(rx(0), y, rx(w-1), y+h-1, C7)
        pyxel.line(rx(w-1), y, rx(0), y+h-1, C7)
        pyxel.line(rx(0), y+1, rx(w-2), y+h-1, C8)
        pyxel.line(rx(w-1), y+1, rx(1), y+h-1, C8)
        pyxel.rect(rx(w//2-1), y, 3, h, C7)
        pyxel.rect(rx(0), y+h//2-1, w, 3, C7)
        pyxel.rect(rx(w//2), y, 1, h, C8)
        pyxel.rect(rx(0), y+h//2, w, 1, C8)
    elif key == "france":
        pyxel.rect(rx(0), y, w//3, h, C1)
        pyxel.rect(rx(w//3), y, w//3, h, C7)
        pyxel.rect(rx(2*w//3), y, w-2*w//3, h, C8)
    elif key == "japan":
        pyxel.rect(rx(0), y, w, h, C7)
        pyxel.circ(rx(w//2), y+h//2, max(2, min(w,h)//4), C8)
    elif key == "italy":
        pyxel.rect(rx(0), y, w//3, h, C3)
        pyxel.rect(rx(w//3), y, w//3, h, C7)
        pyxel.rect(rx(2*w//3), y, w-2*w//3, h, C8)
    elif key == "turkiye":
        pyxel.rect(rx(0), y, w, h, C8)
        pyxel.circ(rx(max(4, w//3)), y+h//2, max(2, h//4), C7)
        pyxel.circ(rx(max(5, w//3+2)), y+h//2, max(2, h//4), C8)
        pyxel.tri(rx(w//2+2), y+h//2, rx(w//2+6), y+h//2-2, rx(w//2+6), y+h//2+2, C7)
    elif key == "brazil":
        pyxel.rect(rx(0), y, w, h, C3)
        pyxel.tri(rx(w//2), y+1, rx(2), y+h//2, rx(w//2), y+h-2, C10)
        pyxel.tri(rx(w//2), y+1, rx(w-2), y+h//2, rx(w//2), y+h-2, C10)
        pyxel.circ(rx(w//2), y+h//2, max(2, min(w,h)//5), C12)
        pyxel.line(rx(w//2-2), y+h//2, rx(w//2+2), y+h//2, C7)
    else:
        _old_draw_flag_v4(key, x, y, w, h, wave)
globals()['draw_flag'] = draw_flag
def _draw_empire_flag(key, x, y, w=30, h=18, wave=0, empire=True):
    ox = int(math.sin(wave) * 1.0)
    def rx(v): return int(x + v + ox)
    x=int(x); y=int(y); w=int(w); h=int(h)
    pyxel.rectb(rx(0), y, w, h, C7)
    if key == 'italy':
        pyxel.rect(rx(0), y, w, h, C8)
        pyxel.rect(rx(0), y, w, 2, C10); pyxel.rect(rx(0), y+h-2, w, 2, C10)
        pyxel.tri(rx(w//2), y+3, rx(w//2-6), y+h-4, rx(w//2+6), y+h-4, C10)
        pyxel.text(rx(max(2,w//2-7)), y+h//2-2, 'SPQR'[:max(1,w//7)], C7)
    elif key == 'turkiye':
        pyxel.rect(rx(0), y, w, h, C8)
        pyxel.circ(rx(w//2-3), y+h//2, max(3,h//3), C7)
        pyxel.circ(rx(w//2), y+h//2, max(3,h//3), C8)
        pyxel.tri(rx(w//2+7), y+h//2, rx(w//2+13), y+h//2-4, rx(w//2+13), y+h//2+4, C7)
        pyxel.line(rx(2), y+2, rx(w-3), y+2, C10)
    elif key == 'china':
        pyxel.rect(rx(0), y, w, h, C10)
        pyxel.line(rx(5), y+h//2, rx(w-6), y+h//2-3, C8)
        pyxel.line(rx(7), y+h//2+3, rx(w-5), y+h//2, C8)
        for i in range(5, w-5, 6): pyxel.pset(rx(i), y+h//2-2+(i//6)%3, C7)
        pyxel.circ(rx(w-7), y+h//2-3, 2, C8)
    elif key == 'japan':
        pyxel.rect(rx(0), y, w, h, C7)
        cx,cy=rx(w//2),y+h//2
        for a in range(0, 360, 30):
            ex = cx + int(math.cos(math.radians(a))*w)
            ey = cy + int(math.sin(math.radians(a))*h)
            pyxel.line(cx, cy, ex, ey, C8)
        pyxel.circ(cx, cy, max(3,h//4), C8)
    elif key == 'germany':
        pyxel.rect(rx(0), y, w, h, C7)
        pyxel.rect(rx(0), y, w, h//3, C0); pyxel.rect(rx(0), y+2*h//3, w, h//3, C8)
        pyxel.rect(rx(w//2-5), y+3, 10, h-6, C10)
        pyxel.rectb(rx(w//2-5), y+3, 10, h-6, C0)
    elif key == 'russia':
        pyxel.rect(rx(0), y, w, h, C7)
        pyxel.rect(rx(0), y+h//3, w, h//3, C10)
        pyxel.rect(rx(0), y+2*h//3, w, h//3, C0)
        pyxel.circ(rx(w//2), y+h//2, 3, C8)
        pyxel.pset(rx(w//2-2), y+h//2, C8); pyxel.pset(rx(w//2+2), y+h//2, C8)
    elif key == 'uk':
        pyxel.rect(rx(0), y, w, h, C1)
        pyxel.rect(rx(w//2-2), y, 4, h, C7); pyxel.rect(rx(0), y+h//2-2, w, 4, C7)
        pyxel.rect(rx(w//2-1), y, 2, h, C8); pyxel.rect(rx(0), y+h//2-1, w, 2, C8)
        pyxel.circ(rx(w//2), y+h//2, 3, C10)
    elif key == 'france':
        pyxel.rect(rx(0), y, w//3, h, C1); pyxel.rect(rx(w//3), y, w//3, h, C7); pyxel.rect(rx(2*w//3), y, w-2*w//3, h, C8)
        pyxel.line(rx(3), y+h-3, rx(w-4), y+3, C10)
        pyxel.tri(rx(w//2), y+3, rx(w//2-4), y+8, rx(w//2+4), y+8, C10)
    elif key == 'brazil':
        pyxel.rect(rx(0), y, w, h, C3)
        pyxel.tri(rx(w//2), y+1, rx(2), y+h//2, rx(w//2), y+h-2, C10)
        pyxel.tri(rx(w//2), y+1, rx(w-2), y+h//2, rx(w//2), y+h-2, C10)
        pyxel.circb(rx(w//2), y+h//2, max(3,h//4), C12)
        pyxel.pset(rx(w//2), y+h//2, C7)
    elif key == 'usa':
        pyxel.rect(rx(0), y, w, h, C1)
        for i in range(0,h,3): pyxel.line(rx(0), y+i, rx(w-1), y+i, C8)
        pyxel.rect(rx(0), y, w//2, h//2+1, C12)
        pyxel.tri(rx(w//2+4), y+3, rx(w//2-2), y+h-4, rx(w//2+10), y+h-4, C10)
    else:
        draw_flag(key, x, y, w, h, wave)
globals()['_draw_empire_flag'] = _draw_empire_flag
def _draw_dev_panel(game):
    _ensure_creative_state(game)
    _ensure_empire_state(game)
    if not game.dev_mode or not game.dev_panel_open:
        return
    rows = _dev_rows(game)
    x, y, w, h = 34, 18, 252, 164
    neon = C14 if (pyxel.frame_count // 6) % 2 else C12
    pyxel.rect(x, y, w, h, C0); pyxel.rectb(x, y, w, h, neon)
    pyxel.rect(x+4, y+4, w-8, 14, C1)
    pyxel.text(x+11, y+8, "UNDERGROUND DEV TERMINAL", C14)
    pyxel.text(x+166, y+8, "MOUSE/KEYS", C6)
    inst_x=x+8; inst_y=y+27
    pyxel.rect(inst_x, inst_y, 78, 92, C1); pyxel.rectb(inst_x, inst_y, 78, 92, C5)
    for j,line in enumerate(["CONTROLS","DRAG SLIDER","UP/DOWN ROW","LEFT/RIGHT SET","SHIFT FAST","S SUPPLY","B BOSS","C CLEAR","R RESET"]):
        pyxel.text(inst_x+5, inst_y+6+j*9, line, C10 if j==0 else C7)
    bay_x=x+96; bay_y=y+27
    pyxel.rect(bay_x, bay_y, 138, 124, C1); pyxel.rectb(bay_x, bay_y, 138, 124, C12)
    start=max(0,min(game.dev_cursor-4,max(0,len(rows)-8)))
    bar_x=bay_x+48; bar_w=72
    for i in range(start,min(start+8,len(rows))):
        label,value,lo,hi,step,apply=rows[i]
        ry=bay_y+13+(i-start)*13
        sel=i==game.dev_cursor
        if sel: pyxel.rect(bay_x+4, ry-3, 130, 11, C0); pyxel.rectb(bay_x+4, ry-3, 130, 11, C14)
        pyxel.text(bay_x+8, ry, ("PICK LV" if label=="LEVEL" else label)[:7], C14 if sel else C7)
        pyxel.text(bay_x+38, ry, str(int(value)), C10 if sel else C6)
        pyxel.rect(bar_x, ry+2, bar_w, 3, C5)
        ratio=0 if hi==lo else clamp((value-lo)/(hi-lo),0,1)
        fill=int(bar_w*ratio)
        pyxel.rect(bar_x, ry+2, fill, 3, C10)
        pyxel.rect(bar_x+fill-2, ry-1, 5, 8, C14 if sel else C7)
    pyxel.text(x+12, y+h-13, "TERMINAL LINK ACTIVE", C6)
globals()['_draw_dev_panel'] = _draw_dev_panel
def _draw_clean_dash_trail(self, sx, sy, empire=False):
    if self.dash_timer <= 0:
        return
    key = self.country['key']
    base_cols = [C12, C7, C5] if not empire else [C10, C14, C7]
    for i in range(1, 7):
        alpha_col = base_cols[i % len(base_cols)]
        ox = -self.dash_dir * i * 6
        yy = sy + 5 + (i % 3) * 4
        pyxel.line(sx + 8 + ox, yy, sx + 8 + ox - self.dash_dir * (8 + i), yy + (i % 2), alpha_col)
        if i < 4:
            pyxel.rect(sx + 5 + ox, sy + 8 + i, 5, 8, C1 if not empire else C10)
            pyxel.pset(sx + 7 + ox, sy + 11 + i, C7)
    for i in range(3):
        fx = sx - self.dash_dir * (18+i*13)
        fy = sy + 2 + i*5
        draw_flag(key, fx, fy, 8, 5, pyxel.frame_count*0.3+i)
def _player_draw_v4(self, cam_x):
    game = globals().get("_active_secret_game")
    empire = bool(game is not None and getattr(game, "empire_mode", False))
    sx=int(self.x-cam_x); sy=int(self.y)
    if self.hurt_flash>0 and (pyxel.frame_count//2)%2==0: return
    _draw_clean_dash_trail(self, sx, sy, empire)
    skin=self.skin
    body=C10 if empire else skin.get('body',C12); cape=C8 if empire else skin.get('cape',C8); visor=C14 if empire else skin.get('visor',C14)
    trim=C14 if empire else skin.get('trim',C7); accent=C7 if empire else skin.get('accent',C10)
    key=self.country['key']
    if self.invuln>0 and (pyxel.frame_count//4)%2==0:
        pyxel.circb(sx+8, sy+11, 13 if not empire else 16, C10 if empire else C12)
    pole_x=sx+20 if self.facing==1 else sx-8
    pyxel.line(pole_x, sy-(8 if empire else 0), pole_x, sy+25, C7)
    if empire:
        _draw_empire_flag(key, pole_x+(1 if self.facing==1 else -29), sy-8, 28, 16, pyxel.frame_count*.16, True)
    else:
        draw_flag(key, pole_x+(1 if self.facing==1 else -13), sy+2, 12, 7, pyxel.frame_count*.16)
    pyxel.tri(sx+5,sy+8,sx-(7 if empire else 4),sy+24,sx+6,sy+22,cape)
    pyxel.tri(sx+12,sy+8,sx+(23 if empire else 20),sy+24,sx+11,sy+22,cape)
    pyxel.rect(sx+4,sy+9,9,14,body); pyxel.rect(sx+6,sy+11,5,10,trim); pyxel.rectb(sx+4,sy+9,9,14,C7)
    pyxel.rect(sx+1,sy+11,3,5,body); pyxel.rect(sx+13,sy+11,3,5,body)
    pyxel.line(sx+15,sy+13,pole_x,sy+8,trim)
    pyxel.rect(sx+5,sy+20,8,2,accent)
    pyxel.rect(sx+3,sy+1,11,8,body); pyxel.rect(sx+5,sy+4,7,3,visor); pyxel.rectb(sx+3,sy+1,11,8,C7)
    if empire: pyxel.tri(sx+4,sy+1,sx+8,sy-6,sx+12,sy+1,C14)
    else: pyxel.tri(sx+4,sy+1,sx+8,sy-4,sx+12,sy+1,skin.get('plume',C8))
    pyxel.pset(sx+6,sy+5,C0); pyxel.pset(sx+11,sy+5,C0)
    step=1 if self.on_ground and abs(self.vx)>0.1 and (self.walk_frame//5)%2==0 else -1
    pyxel.rect(sx+3+step,sy+23,5,3,C0); pyxel.rect(sx+10-step,sy+23,5,3,C0)
    if self.shoot_anim>0:
        mx=sx+19 if self.facing==1 else sx-2; my=sy+11
        pyxel.circ(mx,my,3 if not empire else 4,accent); pyxel.pset(mx,my,C7)
Player.draw = _player_draw_v4
def _tutorial_init_state(self):
    if not hasattr(self, 'tutorial_goal'):
        self.tutorial_goal = 0
        self.tutorial_bot_x = 230
        self.tutorial_dummy_hp = 6
        self.tutorial_did_move = 0
        self.tutorial_did_jump = False
        self.tutorial_did_dash = False
        self.tutorial_did_shoot = False
        self.tutorial_did_special = False
def _tutorial_update_v4(self):
    _tutorial_init_state(self)
    if pyxel.btnp(pyxel.KEY_RETURN):
        self.state='playing'; stop_story_music(); return
    if pyxel.btn(pyxel.KEY_A) or pyxel.btn(pyxel.KEY_D): self.tutorial_did_move += 1
    if pyxel.btnp(pyxel.KEY_W): self.tutorial_did_jump = True
    if pyxel.btnp(pyxel.KEY_SPACE): self.tutorial_did_dash = True
    if pyxel.btnp(pyxel.KEY_LEFT) or pyxel.btnp(pyxel.KEY_RIGHT) or pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.KEY_DOWN):
        self.tutorial_did_shoot = True
        self.tutorial_dummy_hp = max(0, self.tutorial_dummy_hp-1)
        sfx(0,0)
    if pyxel.btnp(pyxel.KEY_J) or pyxel.btnp(pyxel.KEY_K) or pyxel.btnp(pyxel.KEY_SHIFT): self.tutorial_did_special = True
    goals=[self.tutorial_did_move>18,self.tutorial_did_jump,self.tutorial_did_dash,self.tutorial_did_shoot and self.tutorial_dummy_hp<=3,self.tutorial_did_special]
    if self.tutorial_goal < len(goals) and goals[self.tutorial_goal]:
        self.tutorial_goal += 1; sfx(0,3)
    if self.tutorial_goal >= 5 and (pyxel.btnp(pyxel.KEY_RETURN) or pyxel.frame_count%240==0):
        self.state='playing'; stop_story_music()
Game.update_tutorial = _tutorial_update_v4
def _tutorial_draw_v4(self):
    _tutorial_init_state(self)
    pyxel.cls(C0)
    for i in range(0, SCREEN_W, 16): pyxel.line(i, 0, i-80, SCREEN_H, C1)
    pyxel.rect(12, 12, 296, 176, C0); pyxel.rectb(12, 12, 296, 176, C12)
    pyxel.text(94, 22, 'TRAINING LAYER - LIVE CONTROLS', C14)
    pyxel.text(80, 34, 'ENTER SKIPS ANY TIME', C6)
    pyxel.rect(26, 150, 268, 18, C3); pyxel.line(26,150,294,150,C7)
    for bx in [42, 145, 250]:
        pyxel.rect(bx, 133, 34, 17, C4); pyxel.rectb(bx,133,34,17,C7); pyxel.rect(bx+10,122,14,11,C5)
    px=70 + min(55, self.tutorial_did_move)
    py=126 - (16 if self.tutorial_goal==1 and (pyxel.frame_count//10)%2 else 0)
    _draw_skin_preview(px, py-8, self.skin_index, self.current_country()['key'], empire=False)
    dx=218; dy=104
    pyxel.rect(dx,dy,32,20,C5); pyxel.rectb(dx,dy,32,20,C7); pyxel.rect(dx+6,dy+6,20,5,C8)
    pyxel.text(dx+3,dy-10,'TARGET '+str(self.tutorial_dummy_hp)+'/6',C8 if self.tutorial_dummy_hp else C3)
    instructions=[('MOVE', 'A / D', self.tutorial_did_move>18),('JUMP','W',self.tutorial_did_jump),('DASH','SPACE',self.tutorial_did_dash),('SHOOT TARGET','ARROWS',self.tutorial_did_shoot and self.tutorial_dummy_hp<=3),('SPECIALS','J BOMB / K ROCKET / SHIFT WEAPON',self.tutorial_did_special)]
    pyxel.rect(34,58,252,58,C1); pyxel.rectb(34,58,252,58,C5)
    for i,(name,key,done) in enumerate(instructions):
        col=C3 if done else (C10 if i==self.tutorial_goal else C7)
        pyxel.text(44,66+i*10, ('[OK] ' if done else '[  ] ')+name, col)
        pyxel.text(148,66+i*10,key[:26], C6 if not done else C3)
    if self.tutorial_goal>=5:
        pyxel.rect(72,172,176,10,C10); pyxel.text(95,174,'TRAINING COMPLETE - ENTER',C0)
Game.draw_tutorial = _tutorial_draw_v4
def _update_empire_ascension_v4(self):
    _ensure_empire_state(self)
    self.empire_ascension_timer = getattr(self, 'empire_ascension_timer', 0) + 1
    self.empire_anim_timer = self.empire_ascension_timer
    if pyxel.btnp(pyxel.KEY_RETURN) or self.empire_ascension_timer > 430:
        _enter_empire_campaign(self, from_dev=False)
        self.state='playing'; stop_story_music()
Game.update_empire_ascension = _update_empire_ascension_v4
def _start_empire_ascension(game):
    _ensure_empire_state(game)
    game.empire_ascension_timer = 0; game.empire_anim_timer = 0
    game.state='empire_ascension'; play_story_music()
globals()['_start_empire_ascension'] = _start_empire_ascension
def _draw_empire_ascension_v4(self):
    pyxel.cls(C0); _ensure_empire_state(self)
    t=getattr(self,'empire_ascension_timer',0); p=clamp(t/360,0,1)
    key=self.current_country()['key']; name=_better_empire_name(self)
    for yy in range(0,SCREEN_H,5): pyxel.line(0,yy,SCREEN_W,yy,C1 if (yy+t)%20<10 else C0)
    pyxel.rect(8,8,304,184,C0); pyxel.rectb(8,8,304,184,C14 if (t//5)%2 else C10)
    pyxel.text(74,17,'EMPIRE ASCENSION ANIMATION',C14)
    mapx,mapy=23,45
    pyxel.rect(mapx,mapy,110,92,C1); pyxel.rectb(mapx,mapy,110,92,C5)
    pyxel.text(mapx+12,mapy-12,'TERRITORY',C7)
    for r in range(8,int(8+52*p),8): pyxel.circb(mapx+55,mapy+45,r,C3 if r%16 else C10)
    draw_country_outline(key,mapx+20,mapy+18,scale=2,color=C10 if t>80 else C7,fill=t>150)
    for i in range(10):
        ang=i*math.tau/10+t*.04
        rr=min(48,8+p*55)
        pyxel.circ(mapx+55+int(math.cos(ang)*rr),mapy+45+int(math.sin(ang)*rr*.65),2,C10 if i%2 else C8)
    cx,cy=166,98
    pyxel.rect(cx-38,cy-56,76,108,C1); pyxel.rectb(cx-38,cy-56,76,108,C12)
    for i in range(7):
        y=cy-50+((i*19+t*3)%98)
        pyxel.line(cx-32,y,cx+32,y,C10 if i%2 else C14)
    _draw_skin_preview(cx-14,cy-2,self.skin_index,key,empire=t>145)
    if t>150: _draw_empire_flag(key,cx-42,cy-58,84,12,t*.2,True)
    rx,ry=218,45
    pyxel.rect(rx,ry,78,92,C1); pyxel.rectb(rx,ry,78,92,C5)
    pyxel.text(rx+7,ry-12,'WAR POWER',C7)
    power=int(min(100,p*130)); pyxel.rect(rx+12,ry+70,54,6,C0); pyxel.rect(rx+12,ry+70,int(54*power/100),6,C8); pyxel.rectb(rx+12,ry+70,54,6,C7)
    for i in range(3):
        tx=rx+8+i*20; ty=ry+48+int(math.sin((t+i*30)*.08)*2)
        pyxel.rect(tx,ty,14,5,C6); pyxel.rect(tx+4,ty-3,6,3,C5); pyxel.line(tx+8,ty-2,tx+18,ty-5,C7)
    for i in range(2):
        jx=rx+5+((t*2+i*33)%66); jy=ry+13+i*13
        pyxel.tri(jx,jy,jx-9,jy+4,jx-5,jy-5,C12); pyxel.pset(jx-1,jy,C7)
    pyxel.rect(22,148,276,32,C1); pyxel.rectb(22,148,276,32,C10)
    _draw_empire_flag(key,31,154,38,20,t*.15,True)
    pyxel.text(76,153,name[:30],C10)
    pyxel.text(76,164,'UPGRADE: SPEED BOOST + SMALL JUMP BOOST',C14)
    pyxel.text(76,174,'RULE: 1 HEALTH BAR ONLY',C8)
    if t<80: msg='NATION CORE SCANNING'
    elif t<160: msg='TERRITORY EXPANDING'
    elif t<250: msg='MILITARY POWER RISING'
    elif t<340: msg='COMMANDER TRANSFORMING'
    else: msg='ASCENSION COMPLETE'
    pyxel.text(90,33,msg,C7 if t<340 else C14)
    if t>330: pyxel.text(92,185,'PRESS ENTER OR WAIT',C10)
Game.draw_empire_ascension = _draw_empire_ascension_v4
def _ensure_boss_final_state(game):
    if not hasattr(game,'boss_death_timer'): game.boss_death_timer=0
    if not hasattr(game,'final_bunker_active'): game.final_bunker_active=False
    if not hasattr(game,'final_bunker_x'): game.final_bunker_x=0
    if not hasattr(game,'final_bunker_ready'): game.final_bunker_ready=False
_old_v4_reset_run=Game.reset_run
def _reset_run_v4(self):
    _old_v4_reset_run(self); _ensure_boss_final_state(self)
    self.boss_death_timer=0; self.final_bunker_active=False; self.final_bunker_ready=False
Game.reset_run=_reset_run_v4
_old_v4_kill_enemy=Game.kill_enemy
def _kill_enemy_v4(self, enemy):
    was_boss=isinstance(enemy,BossEnemy)
    _old_v4_kill_enemy(self, enemy)
    if was_boss:
        _ensure_boss_final_state(self)
        self.boss_death_timer=150
        self.final_bunker_active=True
        self.final_bunker_ready=False
        self.final_bunker_x=self.player.x+300
        self.distance=min(self.distance, LEVEL_DISTANCE-260)
        self.add_shake(30,5)
        for i in range(40):
            self.particles.append(Particle(enemy.x+enemy.w/2, enemy.y+enemy.h/2, random.uniform(-3,3), random.uniform(-3,1), random.choice([C8,C9,C10,C7]), random.randint(25,65), size=random.choice([1,1,2]), gravity=.04))
        self.add_text(enemy.x, enemy.y-38, 'BOSS CORE COLLAPSE', C14)
Game.kill_enemy=_kill_enemy_v4
_old_v4_update=Game.update
def _update_v4(self):
    _ensure_boss_final_state(self)
    if getattr(self,'state','')=='boss_death':
        self.boss_death_timer-=1
        self.update_shake()
        for e in self.effects: e.update()
        for p in self.particles: p.update()
        self.effects=[e for e in self.effects if e.alive]; self.particles=[p for p in self.particles if p.alive]
        if self.boss_death_timer<=0:
            self.state='playing'; self.final_bunker_ready=True
        return
    _old_v4_update(self)
Game.update=_update_v4
_old_v4_update_playing=Game.update_playing
def _update_playing_v4(self):
    _ensure_boss_final_state(self)
    if self.boss_death_timer>0:
        self.state='boss_death'
        return
    if self.final_bunker_active:
        self.distance=min(self.distance, LEVEL_DISTANCE-90)
    _old_v4_update_playing(self)
    if self.final_bunker_active:
        self.distance=min(self.distance, LEVEL_DISTANCE-90)
        if self.player.x+PLAYER_W/2 >= self.final_bunker_x+28:
            self.final_bunker_active=False; self.final_bunker_ready=False
            self.level_boss_defeated=True
            self.next_level()
Game.update_playing=_update_playing_v4
_old_v4_draw_world=Game.draw_world
def _draw_world_v4(self):
    _old_v4_draw_world(self)
    _ensure_boss_final_state(self)
    if self.final_bunker_active or self.final_bunker_ready:
        x=int(self.final_bunker_x-self.cam_x); gy=self.terrain.ground_y_at(self.final_bunker_x,self.difficulty_value())
        pyxel.rect(x, gy-45, 62, 45, C4); pyxel.rectb(x, gy-45, 62,45,C7)
        pyxel.rect(x+18, gy-26, 25, 26, C0); pyxel.rectb(x+18, gy-26,25,26,C10)
        pyxel.rect(x+6, gy-57, 50, 14, C5); pyxel.rectb(x+6, gy-57,50,14,C7)
        pyxel.text(x+8, gy-53, 'FINAL', C10); pyxel.text(x+32, gy-53, 'BKR', C14)
        pyxel.line(x+31, gy-57, x+31, gy-76, C7)
        _draw_empire_flag(self.current_country()['key'], x+33, gy-76, 24, 14, pyxel.frame_count*.2, getattr(self,'empire_mode',False))
        if (pyxel.frame_count//12)%2==0: pyxel.text(x-12, gy-66, 'ENTER FINAL BUNKER', C10)
Game.draw_world=_draw_world_v4
_old_v4_draw=Game.draw
def _draw_v4(self):
    if getattr(self,'state','')=='boss_death':
        self.draw_world(); self.draw_hud()
        t=max(0,getattr(self,'boss_death_timer',0))
        pyxel.rect(0,0,SCREEN_W,22,C0); pyxel.rect(0,SCREEN_H-24,SCREEN_W,24,C0)
        pyxel.text(80,7,'RIVAL WAR MACHINE DESTROYED',C14 if (pyxel.frame_count//5)%2 else C10)
        pyxel.text(76,SCREEN_H-15,'CORE COLLAPSE - FINAL BUNKER UNLOCKING',C7)
        cx=SCREEN_W//2; cy=90
        r=max(4,int((150-t)*.45))
        pyxel.circb(cx,cy,r,C8); pyxel.circb(cx,cy,max(2,r-8),C10); pyxel.circ(cx,cy,max(1,r//5),C7)
        return
    _old_v4_draw(self)
Game.draw=_draw_v4
_old_v4_boss_draw=BossEnemy.draw
def _boss_draw_v4(self, cam_x):
    _old_v4_boss_draw(self, cam_x)
    sx=int(self.x-cam_x); sy=int(self.y)
    pyxel.rect(sx-4, sy-12, self.w+8, 6, C0)
    pyxel.rectb(sx-4, sy-12, self.w+8, 6, C7)
    pyxel.rect(sx-3, sy-11, int((self.w+6)*max(0,self.hp)/max(1,self.max_hp)), 4, C8)
BossEnemy.draw=_boss_draw_v4
_old_v4_draw_hud=Game.draw_hud
def _draw_hud_v4(self):
    _old_v4_draw_hud(self)
    pyxel.rect(0,184,SCREEN_W,16,C0)
    if getattr(self,'final_bunker_active',False):
        pyxel.text(84,188,'FINAL BUNKER REQUIRED AFTER BOSS',C10)
Game.draw_hud=_draw_hud_v4
def _ooo_v5_update_dev_buffer(self):
    _ensure_secret_bunker_state(self)
    if getattr(self, '_dev_buffer_frame_v5', -1) == pyxel.frame_count:
        return
    self._dev_buffer_frame_v5 = pyxel.frame_count
    if not hasattr(self, 'dev_buffer'):
        self.dev_buffer = ''
    if pyxel.btnp(pyxel.KEY_O):
        self.dev_buffer += 'O'
        self.dev_buffer = self.dev_buffer[-3:]
    if self.dev_buffer == 'OOO':
        self.secret_wall_broken = True
        self.secret_route_unlocked = True
        self.dev_mode = not getattr(self, 'dev_mode', False)
        self.dev_panel_open = self.dev_mode
        self.dev_buffer = ''
        sfx(1, 4)
Game.update_dev_buffer = _ooo_v5_update_dev_buffer
def _draw_dev_panel(game):
    _ensure_creative_state(game)
    _ensure_empire_state(game)
    if not getattr(game, 'dev_mode', False) or not getattr(game, 'dev_panel_open', False):
        return
    rows = _dev_rows(game)
    x, y, w, h = 34, 16, 252, 166
    neon = C14 if (pyxel.frame_count // 6) % 2 else C12
    pyxel.rect(x, y, w, h, C0)
    pyxel.rectb(x, y, w, h, neon)
    pyxel.rect(x + 4, y + 4, w - 8, 14, C1)
    pyxel.text(x + 12, y + 8, 'BUNKER DEV TERMINAL', C14)
    pyxel.text(x + 166, y + 8, 'OOO TOGGLE', C6)
    inst_x, inst_y = x + 8, y + 27
    pyxel.rect(inst_x, inst_y, 78, 92, C1)
    pyxel.rectb(inst_x, inst_y, 78, 92, C5)
    for j, line in enumerate(['CONTROLS','DRAG SLIDER','UP/DOWN ROW','LEFT/RIGHT','SHIFT FAST','S SUPPLY','B BOSS','C CLEAR','R RESET']):
        pyxel.text(inst_x + 5, inst_y + 6 + j * 9, line, C10 if j == 0 else C7)
    bay_x, bay_y = x + 96, y + 27
    pyxel.rect(bay_x, bay_y, 138, 124, C1)
    pyxel.rectb(bay_x, bay_y, 138, 124, C12)
    pyxel.text(bay_x + 8, bay_y + 7, 'LIVE VARIABLES', C14)
    start = max(0, min(game.dev_cursor - 4, max(0, len(rows) - 8)))
    bar_x, bar_w = bay_x + 48, 76
    for i in range(start, min(start + 8, len(rows))):
        label, value, lo, hi, step, _apply = rows[i]
        row_y = bay_y + 24 + (i - start) * 12
        selected = i == game.dev_cursor
        if selected:
            pyxel.rect(bay_x + 5, row_y - 3, 128, 10, C0)
            pyxel.rectb(bay_x + 5, row_y - 3, 128, 10, C14)
        shown = 'PICK LV' if label == 'LEVEL' else label[:7]
        pyxel.text(bay_x + 9, row_y, shown, C14 if selected else C7)
        pyxel.text(bay_x + 38, row_y, str(int(value)), C10 if selected else C6)
        ratio = 0 if hi == lo else clamp((value - lo) / (hi - lo), 0, 1)
        pyxel.rect(bar_x, row_y + 2, bar_w, 3, C5)
        pyxel.rect(bar_x, row_y + 2, int(bar_w * ratio), 3, C10 if label != 'EMPIRE' else C14)
        knob_x = bar_x + int(bar_w * ratio)
        pyxel.rect(knob_x - 2, row_y - 2, 5, 8, C14 if selected else C7)
globals()['_draw_dev_panel'] = _draw_dev_panel
def _tutorial_v5_reset(self):
    self.tutorial_step = 0
    self.tutorial_fake_x = 74
    self.tutorial_fake_y = 126
    self.tutorial_fake_vy = 0
    self.tutorial_balloon_hp = 3
    self.tutorial_shots = []
    self.tutorial_pause_seen = False
    self.tutorial_move_frames = 0
    self.tutorial_dash_flash = 0
    self.tutorial_done_flash = 0
def _ensure_tutorial_v5(self):
    if not hasattr(self, 'tutorial_step') or getattr(self, '_tutorial_v5_ready', False) is False:
        _tutorial_v5_reset(self)
        self._tutorial_v5_ready = True
def _tutorial_v5_update(self):
    _ensure_tutorial_v5(self)
    if pyxel.btnp(pyxel.KEY_RETURN):
        self.state = 'playing'
        stop_story_music()
        return
    if self.tutorial_fake_y < 126 or self.tutorial_fake_vy != 0:
        self.tutorial_fake_vy += 0.55
        self.tutorial_fake_y += self.tutorial_fake_vy
        if self.tutorial_fake_y >= 126:
            self.tutorial_fake_y = 126
            self.tutorial_fake_vy = 0
    step = self.tutorial_step
    if step == 0:
        if pyxel.btnp(pyxel.KEY_W):
            self.tutorial_fake_vy = -7.2
            self.tutorial_step += 1
            self.tutorial_done_flash = 24
            sfx(0, 3)
    elif step == 1:
        if pyxel.btn(pyxel.KEY_D):
            self.tutorial_fake_x = min(130, self.tutorial_fake_x + 1.4)
            self.tutorial_move_frames += 1
        if pyxel.btn(pyxel.KEY_A):
            self.tutorial_fake_x = max(54, self.tutorial_fake_x - 1.4)
            self.tutorial_move_frames += 1
        if self.tutorial_move_frames > 28:
            self.tutorial_step += 1
            self.tutorial_done_flash = 24
            sfx(0, 3)
    elif step == 2:
        if pyxel.btnp(pyxel.KEY_SPACE):
            self.tutorial_dash_flash = 22
            self.tutorial_fake_x = min(155, self.tutorial_fake_x + 28)
            self.tutorial_step += 1
            self.tutorial_done_flash = 24
            sfx(0, 5)
    elif step == 3:
        if pyxel.btnp(pyxel.KEY_RIGHT) or pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.KEY_LEFT) or pyxel.btnp(pyxel.KEY_DOWN):
            sx = self.tutorial_fake_x + 16
            sy = self.tutorial_fake_y + 7
            vx = 4 if not pyxel.btnp(pyxel.KEY_LEFT) else -4
            vy = -2.5 if pyxel.btnp(pyxel.KEY_UP) else (2.5 if pyxel.btnp(pyxel.KEY_DOWN) else 0)
            self.tutorial_shots.append([sx, sy, vx, vy, 42])
            sfx(0, 0)
        bx, by = 226, 82
        for shot in self.tutorial_shots:
            shot[0] += shot[2]
            shot[1] += shot[3]
            shot[4] -= 1
            if abs(shot[0] - bx) < 13 and abs(shot[1] - by) < 17 and self.tutorial_balloon_hp > 0:
                self.tutorial_balloon_hp -= 1
                shot[4] = 0
                sfx(1, 2)
        self.tutorial_shots = [s for s in self.tutorial_shots if s[4] > 0]
        if self.tutorial_balloon_hp <= 0:
            self.tutorial_step += 1
            self.tutorial_done_flash = 30
            for _ in range(10):
                self.particles.append(Particle(bx, by, random.uniform(-1.5,1.5), random.uniform(-2,0), random.choice([C8,C10,C14,C7]), random.randint(18,35), size=1, gravity=.05))
    elif step == 4:
        if pyxel.btnp(pyxel.KEY_J) or pyxel.btnp(pyxel.KEY_K) or pyxel.btnp(pyxel.KEY_SHIFT):
            self.tutorial_step += 1
            self.tutorial_done_flash = 24
            sfx(0, 3)
    elif step == 5:
        if pyxel.btnp(pyxel.KEY_P):
            self.tutorial_pause_seen = True
            self.tutorial_step += 1
            self.tutorial_done_flash = 36
            sfx(1, 4)
    else:
        if pyxel.btnp(pyxel.KEY_RETURN):
            self.state = 'playing'
            stop_story_music()
    if self.tutorial_dash_flash > 0:
        self.tutorial_dash_flash -= 1
    if self.tutorial_done_flash > 0:
        self.tutorial_done_flash -= 1
    for p in self.particles:
        p.update()
    self.particles = [p for p in self.particles if p.alive]
Game.update_tutorial = _tutorial_v5_update
def _tutorial_v5_draw(self):
    _ensure_tutorial_v5(self)
    pyxel.cls(C0)
    for i in range(0, SCREEN_W, 18):
        pyxel.line(i, 0, i - 92, SCREEN_H, C1)
    pyxel.rect(10, 10, 300, 180, C0)
    pyxel.rectb(10, 10, 300, 180, C12)
    pyxel.text(104, 20, 'TRAINING LAYER', C14)
    pyxel.text(224, 20, 'ENTER SKIP', C6)
    pyxel.rect(24, 148, 272, 18, C3)
    pyxel.line(24, 148, 296, 148, C7)
    pyxel.rect(34, 130, 38, 18, C4); pyxel.rectb(34,130,38,18,C7)
    pyxel.rect(162, 133, 34, 15, C5); pyxel.rectb(162,133,34,15,C7)
    if self.tutorial_dash_flash > 0:
        for i in range(4):
            pyxel.line(int(self.tutorial_fake_x - 8 - i*8), int(self.tutorial_fake_y + 6 + i), int(self.tutorial_fake_x + 8 - i*8), int(self.tutorial_fake_y + 6 + i), C10 if i < 2 else C7)
    _draw_skin_preview(int(self.tutorial_fake_x), int(self.tutorial_fake_y - 8), self.skin_index, self.current_country()['key'], empire=False)
    bx, by = 226, 82
    string_y = 116
    pyxel.line(bx, by + 12, bx, string_y, C7)
    col = C8 if self.tutorial_balloon_hp > 0 else C5
    pyxel.circ(bx, by, 13, col)
    pyxel.circb(bx, by, 13, C7)
    pyxel.text(bx - 15, by - 24, 'BALLOON', C10)
    pyxel.text(bx - 8, by - 3, str(max(0,self.tutorial_balloon_hp)), C7)
    for sx, sy, vx, vy, life in self.tutorial_shots:
        pyxel.circ(int(sx), int(sy), 2, C10)
    steps = [
        ('PRESS W TO JUMP', 'Jump once. The lesson continues after you press W.'),
        ('HOLD A / D TO MOVE', 'Move the commander until the bar fills.'),
        ('PRESS SPACE TO DASH', 'Dash forward and watch the speed trail.'),
        ('ARROWS TO SHOOT BALLOON', 'Shoot the balloon until it pops.'),
        ('TRY A SPECIAL', 'J = bomb, K = rocket, SHIFT = weapon switch.'),
        ('PRESS P FOR PAUSE', 'Pause exists during real gameplay too.'),
        ('TRAINING COMPLETE', 'Press ENTER to begin the run.'),
    ]
    idx = min(self.tutorial_step, len(steps)-1)
    title, desc = steps[idx]
    pyxel.rect(34, 46, 252, 38, C1)
    pyxel.rectb(34, 46, 252, 38, C14 if self.tutorial_done_flash == 0 else C10)
    pyxel.text(48, 55, title, C14)
    pyxel.text(48, 68, desc[:42], C7)
    for i in range(7):
        x = 48 + i * 32
        pyxel.rect(x, 101, 22, 7, C3 if i < self.tutorial_step else C5)
        if i == self.tutorial_step:
            pyxel.rectb(x-1, 100, 24, 9, C14)
    pyxel.text(48, 114, 'W  A/D  SPACE  ARROWS  J/K/SHIFT  P', C6)
    if self.tutorial_step >= 6:
        pyxel.rect(74, 172, 172, 10, C10)
        pyxel.text(100, 174, 'READY - PRESS ENTER', C0)
    for p in self.particles:
        p.draw(0)
Game.draw_tutorial = _tutorial_v5_draw
def _boss_init_v5(self, x):
    Enemy.__init__(self, x, 64, 76, 74, 70, 650)
    self.fire_cd = 80
    self.missile_cd = 9999
    self.dir = -1
    self.wave = 0
    self.laser_charge = 0
    self.laser_timer = 0
    self.laser_y = 92
    self.laser_hit_cd = 0
BossEnemy.__init__ = _boss_init_v5
def _boss_update_v5(self, game):
    self.wave += 0.035
    self.y = 64 + math.sin(self.wave) * 6
    self.x += self.dir * 0.45
    if self.x < game.player.x + 92:
        self.dir = 1
    if self.x > game.player.x + 205:
        self.dir = -1
    self.update_flash()
    if self.laser_hit_cd > 0:
        self.laser_hit_cd -= 1
    if self.laser_timer > 0:
        self.laser_timer -= 1
        px, py, pw, ph = game.player.hurtbox()
        beam_y = self.laser_y
        if self.laser_hit_cd <= 0 and px < self.x + 36 and py < beam_y + 5 and py + ph > beam_y - 5:
            game.player_hit(1)
            self.laser_hit_cd = 35
        return
    if self.laser_charge > 0:
        self.laser_charge -= 1
        self.laser_y = game.player.y + 10
        if self.laser_charge <= 0:
            self.laser_timer = 22
            self.fire_cd = 105 if self.hp >= 28 else 78
            game.add_shake(6, 2)
            sfx(1, 7)
        return
    self.fire_cd -= 1
    if self.fire_cd <= 0:
        self.laser_charge = 42
        self.laser_y = game.player.y + 10
        self.fire_cd = 9999
        game.add_text(self.x - 10, self.y - 14, 'LASER LOCK', C8)
    self.missile_cd -= 1
    if self.missile_cd <= 0:
        self.missile_cd = 240
        game.enemies.append(MissileEnemy(self.x + self.w/2, 1.7))
BossEnemy.update = _boss_update_v5
_old_boss_draw_for_v5 = BossEnemy.draw
def _boss_draw_v5(self, cam_x):
    _old_boss_draw_for_v5(self, cam_x)
    sx, sy = int(self.x - cam_x), int(self.y)
    pyxel.rect(42, 4, 236, 12, C0)
    pyxel.rectb(42, 4, 236, 12, C7)
    pyxel.rect(44, 6, int(232 * max(0,self.hp) / max(1,self.max_hp)), 8, C8)
    pyxel.text(102, 7, 'BOSS CORE', C7)
    if getattr(self, 'laser_charge', 0) > 0:
        yy = int(self.laser_y)
        pyxel.line(0, yy, SCREEN_W, yy, C8 if (pyxel.frame_count//3)%2 else C10)
        pyxel.text(max(4, sx - 18), yy - 9, 'WARNING', C8)
    if getattr(self, 'laser_timer', 0) > 0:
        yy = int(self.laser_y)
        pyxel.rect(0, yy - 3, SCREEN_W, 7, C8)
        pyxel.line(0, yy, SCREEN_W, yy, C10)
        pyxel.line(0, yy - 4, SCREEN_W, yy - 4, C7)
        pyxel.line(0, yy + 4, SCREEN_W, yy + 4, C7)
BossEnemy.draw = _boss_draw_v5
def _ensure_boss_v5(game):
    if not hasattr(game, 'boss_death_timer'):
        game.boss_death_timer = 0
    if not hasattr(game, 'boss_complete_pending'):
        game.boss_complete_pending = False
    game.final_bunker_active = False
    game.final_bunker_ready = False
_old_kill_enemy_before_v5 = Game.kill_enemy
def _kill_enemy_v5(self, enemy):
    if not isinstance(enemy, BossEnemy):
        return _old_kill_enemy_before_v5(self, enemy)
    if getattr(enemy, '_v5_death_done', False):
        return
    enemy._v5_death_done = True
    enemy.alive = False
    _ensure_boss_v5(self)
    self.spawn_explosion(enemy.x + enemy.w / 2, enemy.y + enemy.h / 2, 34, 'nuke')
    self.player.add_score(enemy.score)
    self.add_text(enemy.x, enemy.y - 32, 'BOSS DESTROYED', C14)
    self.add_text(enemy.x, enemy.y - 20, 'CORE COLLAPSE', C10)
    for _ in range(14):
        self.coins.append(Coin(enemy.x + enemy.w / 2, enemy.y + enemy.h / 2, 1))
    for _ in range(42):
        self.particles.append(Particle(enemy.x + enemy.w/2, enemy.y + enemy.h/2, random.uniform(-3.2,3.2), random.uniform(-3.0,1.2), random.choice([C8,C9,C10,C14,C7]), random.randint(24,70), size=random.choice([1,1,2]), gravity=.04))
    self.enemies = [e for e in self.enemies if not isinstance(e, BossEnemy)]
    self.enemy_bullets.clear()
    self.level_boss_defeated = True
    self.level_boss_spawned = True
    self.boss_complete_pending = True
    self.boss_death_timer = 150
    self.final_bunker_active = False
    self.final_bunker_ready = False
    self.distance = min(self.distance, LEVEL_DISTANCE - 80)
    self.state = 'boss_death'
    self.add_shake(35, 5)
    sfx(0, 7)
Game.kill_enemy = _kill_enemy_v5
_old_update_before_v5 = Game.update
def _update_v5(self):
    _ensure_boss_v5(self)
    if getattr(self, 'state', '') != 'playing':
        try:
            self.update_dev_buffer()
        except Exception:
            pass
    if getattr(self, 'state', '') == 'boss_death':
        self.boss_death_timer -= 1
        self.update_shake()
        for e in self.effects:
            e.update()
        for p in self.particles:
            p.update()
        for t in self.texts:
            t.update()
        self.effects = [e for e in self.effects if e.alive]
        self.particles = [p for p in self.particles if p.alive]
        self.texts = [t for t in self.texts if t.alive]
        if self.boss_death_timer <= 0:
            self.boss_complete_pending = False
            self.final_bunker_active = False
            self.final_bunker_ready = False
            self.enemy_bullets.clear()
            self.enemies = [e for e in self.enemies if not isinstance(e, BossEnemy)]
            self.next_level()
        return
    return _old_update_before_v5(self)
Game.update = _update_v5
_old_update_playing_before_v5 = Game.update_playing
def _update_playing_v5(self):
    _ensure_boss_v5(self)
    self.final_bunker_active = False
    self.final_bunker_ready = False
    if getattr(self, 'boss_complete_pending', False):
        self.state = 'boss_death'
        return
    if self.is_campaign() and _is_boss_gate_level(self) and not getattr(self, 'level_boss_defeated', False):
        if self.distance >= LEVEL_DISTANCE - 40:
            self.distance = LEVEL_DISTANCE - 40
        if not any(isinstance(e, BossEnemy) and e.alive for e in self.enemies) and not getattr(self, 'level_boss_spawned', False):
            self.level_boss_spawned = True
            self.enemies.append(BossEnemy(self.player.x + 215))
            self.add_text(self.player.x + 72, 34, 'BOSS GATE', C8)
    _old_update_playing_before_v5(self)
    self.final_bunker_active = False
    self.final_bunker_ready = False
    if getattr(self, 'boss_complete_pending', False):
        self.state = 'boss_death'
        self.enemies = [e for e in self.enemies if not isinstance(e, BossEnemy)]
Game.update_playing = _update_playing_v5
_old_draw_before_v5 = Game.draw
def _draw_v5(self):
    if getattr(self, 'state', '') == 'boss_death':
        self.draw_world()
        self.draw_hud()
        t = max(0, getattr(self, 'boss_death_timer', 0))
        pyxel.rect(0, 0, SCREEN_W, 24, C0)
        pyxel.rect(0, SCREEN_H - 25, SCREEN_W, 25, C0)
        pyxel.text(82, 7, 'BOSS DEATH CINEMATIC', C14 if (pyxel.frame_count//5)%2 else C10)
        pyxel.text(82, SCREEN_H - 16, 'CORE MELTDOWN - NEXT LEVEL UNLOCKING', C7)
        cx, cy = SCREEN_W // 2, 88
        radius = max(8, int((150 - t) * 0.50))
        pyxel.circb(cx, cy, radius, C8)
        pyxel.circb(cx, cy, max(2, radius - 10), C10)
        pyxel.circ(cx, cy, max(2, radius // 5), C7)
        for i in range(8):
            ang = i * math.tau / 8 + pyxel.frame_count * .08
            pyxel.line(cx, cy, cx + int(math.cos(ang) * radius), cy + int(math.sin(ang) * radius), C8 if i % 2 else C10)
        return
    _old_draw_before_v5(self)
Game.draw = _draw_v5
_old_world_before_v5 = Game.draw_world
def _draw_world_v5(self):
    old_active = getattr(self, 'final_bunker_active', False)
    old_ready = getattr(self, 'final_bunker_ready', False)
    self.final_bunker_active = False
    self.final_bunker_ready = False
    _old_world_before_v5(self)
    self.final_bunker_active = False
    self.final_bunker_ready = False
Game.draw_world = _draw_world_v5
def _draw_v5_dash_trail(self, sx, sy, empire=False):
    if self.dash_timer <= 0:
        return
    direction = -self.facing
    cols = [C10, C14, C7] if empire else [C12, C10, C7]
    for i in range(5):
        ox = sx + direction * (7 + i * 7)
        yy = sy + 9 + (i % 2)
        pyxel.line(ox, yy, ox + direction * 12, yy, cols[i % len(cols)])
        pyxel.pset(ox + direction * 14, yy + 2, cols[(i + 1) % len(cols)])
    if empire:
        pyxel.circb(sx + 8 - self.facing * 14, sy + 12, 8, C10)
def _draw_empire_avatar(key, x, y, scale=1, t=0):
    pyxel.rect(x+7, y+16, 14, 20, C10)
    pyxel.rect(x+10, y+18, 8, 14, C14)
    pyxel.rectb(x+7, y+16, 14, 20, C7)
    pyxel.rect(x+4, y+18, 5, 7, C10)
    pyxel.rect(x+20, y+18, 5, 7, C10)
    pyxel.rect(x+7, y+36, 6, 6, C0)
    pyxel.rect(x+16, y+36, 6, 6, C0)
    pyxel.rect(x+7, y+7, 14, 10, C10)
    pyxel.rect(x+10, y+11, 8, 3, C7)
    pyxel.pset(x+12, y+12, C0); pyxel.pset(x+17, y+12, C0)
    if key == 'italy':
        pyxel.tri(x+5,y+8,x+14,y-4,x+23,y+8,C8)
        pyxel.line(x+8,y+4,x+20,y+4,C10)
        pyxel.rect(x-4,y+19,8,15,C8); pyxel.rectb(x-4,y+19,8,15,C10)
        pyxel.text(x-3,y+24,'SP',C10)
    elif key == 'turkiye':
        pyxel.circ(x+14,y+6,8,C8); pyxel.circ(x+17,y+6,6,C10)
        pyxel.rect(x+8,y+6,13,4,C7)
        pyxel.circ(x+26,y+21,6,C8); pyxel.circ(x+28,y+21,5,C10); pyxel.pset(x+31,y+19,C7)
    elif key == 'china':
        pyxel.rect(x+5,y+3,18,6,C10); pyxel.rectb(x+5,y+3,18,6,C7)
        pyxel.line(x+6,y+2,x+22,y+2,C8)
        pyxel.line(x+4,y+23,x-4,y+18,C10); pyxel.line(x+24,y+23,x+32,y+18,C10)
        pyxel.text(x+1,y+2,'Q',C8)
    elif key == 'japan':
        pyxel.rect(x+5,y+5,18,7,C8); pyxel.rectb(x+5,y+5,18,7,C7)
        pyxel.line(x+5,y+5,x+23,y+12,C7)
        pyxel.line(x+23,y+5,x+5,y+12,C7)
        pyxel.line(x+23,y+18,x+32,y+10,C7)
    elif key == 'uk':
        pyxel.tri(x+6,y+8,x+14,y-2,x+22,y+8,C10)
        pyxel.rect(x+9,y+1,10,3,C8)
        pyxel.line(x-3,y+14,x-10,y+30,C12); pyxel.line(x+31,y+14,x+38,y+30,C12)
    elif key == 'france':
        pyxel.tri(x+5,y+8,x+14,y-3,x+23,y+8,C7)
        pyxel.pset(x+14,y-4,C10)
        pyxel.line(x+2,y+18,x-8,y+11,C12); pyxel.line(x+26,y+18,x+36,y+11,C8)
    elif key == 'germany':
        pyxel.rect(x+5,y+3,18,7,C0); pyxel.rectb(x+5,y+3,18,7,C7)
        pyxel.tri(x+6,y+10,x+14,y-2,x+22,y+10,C10)
        pyxel.line(x+14,y-2,x+14,y+7,C7)
    elif key == 'russia':
        pyxel.rect(x+5,y+4,18,6,C7); pyxel.rect(x+8,y+1,12,4,C10)
        pyxel.line(x+1,y+18,x-8,y+25,C7); pyxel.line(x+28,y+18,x+37,y+25,C7)
    elif key == 'brazil':
        pyxel.circ(x+14,y+5,8,C3); pyxel.circb(x+14,y+5,8,C10)
        pyxel.tri(x+1,y+27,x+7,y+17,x+7,y+36,C3); pyxel.tri(x+27,y+27,x+21,y+17,x+21,y+36,C3)
    else:
        pyxel.tri(x+5,y+8,x+14,y-4,x+23,y+8,C12)
        pyxel.pset(x+14,y-5,C7)
        pyxel.line(x+1,y+17,x-8,y+11,C12); pyxel.line(x+27,y+17,x+36,y+11,C8)
    pyxel.line(x+29, y-8, x+29, y+42, C7)
    _draw_empire_flag(key, x+31, y-8, 36, 21, t*.18, True)
def _player_draw_v5(self, cam_x):
    game = globals().get('_active_secret_game')
    empire = bool(game is not None and getattr(game, 'empire_mode', False))
    sx, sy = int(self.x - cam_x), int(self.y)
    if self.hurt_flash > 0 and (pyxel.frame_count // 2) % 2 == 0:
        return
    if self.invuln > 0 and (pyxel.frame_count // 4) % 2 == 0:
        pyxel.circb(sx+8, sy+11, 14 if not empire else 17, C3 if not empire else C10)
    _draw_v5_dash_trail(self, sx, sy, empire)
    if empire:
        _draw_empire_avatar(self.country['key'], sx-6, sy-12, t=pyxel.frame_count)
        return
    skin=self.skin
    body=skin['body']; cape=skin['cape']; visor=skin['visor']; trim=skin.get('trim',C7); accent=skin.get('accent',C10)
    pyxel.rect(sx+3, sy+24, 12, 2, C0)
    pole_x=sx+20 if self.facing==1 else sx-8
    pyxel.line(pole_x, sy, pole_x, sy+25, C7)
    draw_flag(self.country['key'], pole_x + (1 if self.facing==1 else -13), sy+1, 12, 7, pyxel.frame_count*.16)
    pyxel.tri(sx+5, sy+8, sx-4, sy+24, sx+6, sy+22, cape)
    pyxel.tri(sx+12, sy+8, sx+20, sy+24, sx+11, sy+22, cape)
    pyxel.rect(sx+4, sy+9, 9, 14, body)
    pyxel.rect(sx+6, sy+11, 5, 10, trim)
    pyxel.rectb(sx+4, sy+9, 9, 14, C7)
    pyxel.rect(sx+3, sy+1, 11, 8, body)
    pyxel.rect(sx+5, sy+4, 7, 3, visor)
    pyxel.rectb(sx+3, sy+1, 11, 8, C7)
    pyxel.tri(sx+4, sy+1, sx+8, sy-4, sx+12, sy+1, skin.get('plume',C8))
    pyxel.pset(sx+6, sy+5, C0); pyxel.pset(sx+11, sy+5, C0)
    step=1 if self.on_ground and abs(self.vx)>0.1 and (self.walk_frame//5)%2==0 else -1
    pyxel.rect(sx+3+step, sy+23, 5, 3, C0)
    pyxel.rect(sx+10-step, sy+23, 5, 3, C0)
    if self.shoot_anim>0:
        mx=sx+19 if self.facing==1 else sx-2
        pyxel.circ(mx, sy+11, 3, accent)
Player.draw = _player_draw_v5
def _update_empire_ascension_v5(self):
    _ensure_empire_state(self)
    self.empire_ascension_timer = getattr(self, 'empire_ascension_timer', 0) + 1
    self.empire_anim_timer = self.empire_ascension_timer
    if self.empire_ascension_timer > 520 or (self.empire_ascension_timer > 320 and pyxel.btnp(pyxel.KEY_RETURN)):
        _enter_empire_campaign(self, from_dev=False)
        self.state = 'playing'
        stop_story_music()
Game.update_empire_ascension = _update_empire_ascension_v5
def _draw_empire_ascension_v5(self):
    pyxel.cls(C0)
    _ensure_empire_state(self)
    t = getattr(self, 'empire_ascension_timer', 0)
    p = clamp(t / 440, 0, 1)
    key = self.current_country()['key']
    name = _better_empire_name(self)
    for yy in range(0, SCREEN_H, 4):
        pyxel.line(0, yy, SCREEN_W, yy, C1 if (yy + t) % 24 < 11 else C0)
    pyxel.rect(6, 6, 308, 188, C0)
    pyxel.rectb(6, 6, 308, 188, C14 if (t//8)%2 else C10)
    pyxel.text(82, 14, 'EMPIRE UPGRADE SEQUENCE', C14)
    if t < 90: stage = '1/5  SCANNING NATIONAL CORE'
    elif t < 180: stage = '2/5  TERRITORY EXPANDING'
    elif t < 270: stage = '3/5  MILITARY POWER RISING'
    elif t < 360: stage = '4/5  COMMANDER AVATAR FORGING'
    else: stage = '5/5  EMPIRE ONLINE'
    pyxel.text(82, 28, stage, C7 if t < 360 else C14)
    mapx, mapy = 20, 48
    pyxel.rect(mapx, mapy, 100, 88, C1); pyxel.rectb(mapx, mapy, 100, 88, C5)
    pyxel.text(mapx+9, mapy-10, 'TERRITORY GROWTH', C7)
    for i in range(8):
        rr = int(8 + min(44, p * 56) - i*3)
        if rr > 4:
            pyxel.circb(mapx+50, mapy+42, rr, C3 if i%2 else C10)
    draw_country_outline(key, mapx+16, mapy+18, scale=2, color=C10, fill=t>145)
    pyxel.rect(mapx+10, mapy+72, 80, 5, C0)
    pyxel.rect(mapx+10, mapy+72, int(80*p), 5, C3)
    pyxel.text(mapx+10, mapy+80, '+' + str(int(p*100)) + '% CONTROL', C10)
    cx, cy = 160, 96
    pyxel.rect(cx-43, cy-59, 86, 116, C1); pyxel.rectb(cx-43, cy-59, 86, 116, C12)
    for i in range(6):
        y = cy - 52 + ((t*3 + i*18) % 100)
        pyxel.line(cx-35, y, cx+35, y, C10 if i%2 else C14)
    if t < 220:
        _draw_skin_preview(cx-14, cy-5, self.skin_index, key, empire=False)
        pyxel.text(cx-33, cy+45, 'OLD COMMANDER', C6)
    elif t < 300:
        _draw_skin_preview(cx-14, cy-5, self.skin_index, key, empire=False)
        pyxel.circb(cx, cy+16, 34 + (t%12), C10)
        pyxel.circb(cx, cy+16, 24 + (t%8), C14)
        pyxel.text(cx-26, cy+45, 'FORGING...', C10)
    else:
        _draw_empire_avatar(key, cx-18, cy-24, t=t)
        pyxel.text(cx-36, cy+45, 'UNIQUE EMPIRE AVATAR', C14)
    rx, ry = 217, 48
    pyxel.rect(rx, ry, 78, 88, C1); pyxel.rectb(rx, ry, 78, 88, C5)
    pyxel.text(rx+11, ry-10, 'BOOSTS', C7)
    spd_old, spd_new = 45, 58
    jmp_old, jmp_new = 42, 52
    grow = clamp((t - 250) / 110, 0, 1)
    pyxel.text(rx+8, ry+11, 'SPEED', C10)
    pyxel.rect(rx+8, ry+21, 58, 5, C0); pyxel.rect(rx+8, ry+21, int(spd_old*.58), 5, C5); pyxel.rect(rx+8, ry+21, int((spd_old+(spd_new-spd_old)*grow)*.58), 5, C10)
    pyxel.text(rx+8, ry+34, 'JUMP', C14)
    pyxel.rect(rx+8, ry+44, 58, 5, C0); pyxel.rect(rx+8, ry+44, int(jmp_old*.58), 5, C5); pyxel.rect(rx+8, ry+44, int((jmp_old+(jmp_new-jmp_old)*grow)*.58), 5, C14)
    pyxel.text(rx+8, ry+61, '+ SPEED', C10 if grow>0 else C5)
    pyxel.text(rx+8, ry+72, '+ SMALL JUMP', C14 if grow>0 else C5)
    pyxel.rect(20, 146, 280, 35, C1); pyxel.rectb(20, 146, 280, 35, C10)
    _draw_empire_flag(key, 30, 153, 48, 22, t*.18, True)
    pyxel.text(87, 153, name[:31], C10)
    pyxel.text(87, 164, 'NEW AVATAR + SPEED BOOST + SMALL JUMP BOOST', C14)
    pyxel.text(87, 174, 'EMPIRE GAUNTLET: ONLY 1 HEALTH BAR', C8)
    if t > 320:
        pyxel.text(94, 186, 'PRESS ENTER TO CONTINUE', C10)
Game.draw_empire_ascension = _draw_empire_ascension_v5
def _v6_disable_final_bunker(game):
    game.final_bunker_active = False
    game.final_bunker_ready = False
    game.final_bunker_x = -999999
_old_v6_draw_world = Game.draw_world
def _draw_world_v6_no_bunker(self):
    _v6_disable_final_bunker(self)
    _old_v6_draw_world(self)
    _v6_disable_final_bunker(self)
Game.draw_world = _draw_world_v6_no_bunker
_old_v6_next_level = Game.next_level
def _next_level_v6(self):
    self.level_boss_defeated = False
    self.level_boss_spawned = False
    self.boss_complete_pending = False
    self.boss_death_timer = 0
    _v6_disable_final_bunker(self)
    self.enemies = [e for e in self.enemies if not isinstance(e, BossEnemy)]
    return _old_v6_next_level(self)
Game.next_level = _next_level_v6
_old_v6_reset_run = Game.reset_run
def _reset_run_v6(self):
    _old_v6_reset_run(self)
    self.level_boss_defeated = False
    self.level_boss_spawned = False
    self.boss_complete_pending = False
    self.boss_death_timer = 0
    _v6_disable_final_bunker(self)
    if getattr(self, 'state', '') == 'tutorial':
        self._tutorial_v6_ready = False
Game.reset_run = _reset_run_v6
def _tutorial_v6_reset(self):
    self.tutorial_step = 0
    self.tutorial_fake_x = 66
    self.tutorial_fake_y = 128
    self.tutorial_fake_vy = 0.0
    self.tutorial_fake_ground = 128
    self.tutorial_face = 1
    self.tutorial_move_frames = 0
    self.tutorial_dash_timer = 0
    self.tutorial_balloon_hp = 4
    self.tutorial_balloon_pop = 0
    self.tutorial_shots = []
    self.tutorial_confirm_timer = 0
    self.tutorial_special_seen = ''
    self.tutorial_pause_overlay = 0
    self._tutorial_v6_ready = True
def _ensure_tutorial_v6(self):
    if not getattr(self, '_tutorial_v6_ready', False):
        _tutorial_v6_reset(self)
def _tutorial_v6_complete_step(self):
    self.tutorial_step += 1
    self.tutorial_confirm_timer = 26
    sfx(0, 3)
def _tutorial_v6_update(self):
    _ensure_tutorial_v6(self)
    if pyxel.btnp(pyxel.KEY_RETURN):
        self.state = 'playing'
        stop_story_music()
        return
    if self.tutorial_fake_y < self.tutorial_fake_ground or self.tutorial_fake_vy != 0:
        self.tutorial_fake_vy += 0.50
        self.tutorial_fake_y += self.tutorial_fake_vy
        if self.tutorial_fake_y >= self.tutorial_fake_ground:
            self.tutorial_fake_y = self.tutorial_fake_ground
            self.tutorial_fake_vy = 0
    step = self.tutorial_step
    if step == 0:
        if pyxel.btnp(pyxel.KEY_W) and self.tutorial_fake_y >= self.tutorial_fake_ground:
            self.tutorial_fake_vy = -7.6
            self.tutorial_step = 1
            self.tutorial_confirm_timer = 18
            sfx(0, 3)
    elif step == 1:
        moved = False
        if pyxel.btn(pyxel.KEY_D):
            self.tutorial_fake_x = min(145, self.tutorial_fake_x + 1.6)
            self.tutorial_face = 1
            moved = True
        if pyxel.btn(pyxel.KEY_A):
            self.tutorial_fake_x = max(44, self.tutorial_fake_x - 1.6)
            self.tutorial_face = -1
            moved = True
        if moved:
            self.tutorial_move_frames += 1
        if self.tutorial_move_frames >= 34:
            _tutorial_v6_complete_step(self)
    elif step == 2:
        fired = False
        vx, vy = 0, 0
        if pyxel.btnp(pyxel.KEY_RIGHT): vx, vy, fired = 5.0, 0, True
        elif pyxel.btnp(pyxel.KEY_LEFT): vx, vy, fired = -5.0, 0, True
        elif pyxel.btnp(pyxel.KEY_UP): vx, vy, fired = 4.0, -2.6, True
        elif pyxel.btnp(pyxel.KEY_DOWN): vx, vy, fired = 4.0, 2.6, True
        if fired:
            self.tutorial_face = 1 if vx >= 0 else -1
            self.tutorial_shots.append([self.tutorial_fake_x + 18, self.tutorial_fake_y + 3, vx, vy, 72])
            sfx(0, 0)
        bx, by = 236, 83
        for s in self.tutorial_shots:
            s[0] += s[2]
            s[1] += s[3]
            s[4] -= 1
            if self.tutorial_balloon_hp > 0 and abs(s[0] - bx) < 15 and abs(s[1] - by) < 18:
                self.tutorial_balloon_hp -= 1
                s[4] = 0
                self.tutorial_balloon_pop = 16
                sfx(1, 2)
                for _ in range(8):
                    self.particles.append(Particle(bx, by, random.uniform(-1.8,1.8), random.uniform(-2.0,.3), random.choice([C8,C10,C14,C7]), random.randint(16,34), size=1, gravity=.04))
        self.tutorial_shots = [s for s in self.tutorial_shots if s[4] > 0]
        if self.tutorial_balloon_hp <= 0:
            _tutorial_v6_complete_step(self)
    elif step == 3:
        if pyxel.btnp(pyxel.KEY_SPACE):
            self.tutorial_dash_timer = 28
            self.tutorial_fake_x = min(190, self.tutorial_fake_x + 38)
            _tutorial_v6_complete_step(self)
            sfx(0, 5)
    elif step == 4:
        if pyxel.btnp(pyxel.KEY_J):
            self.tutorial_special_seen = 'BOMB READY'
            _tutorial_v6_complete_step(self)
        elif pyxel.btnp(pyxel.KEY_K):
            self.tutorial_special_seen = 'ROCKET READY'
            _tutorial_v6_complete_step(self)
        elif pyxel.btnp(pyxel.KEY_SHIFT):
            self.tutorial_special_seen = 'WEAPON SWITCHED'
            _tutorial_v6_complete_step(self)
    elif step == 5:
        if pyxel.btnp(pyxel.KEY_P):
            self.tutorial_pause_overlay = 45
            _tutorial_v6_complete_step(self)
            sfx(1, 4)
    else:
        pass
    if self.tutorial_dash_timer > 0:
        self.tutorial_dash_timer -= 1
    if self.tutorial_balloon_pop > 0:
        self.tutorial_balloon_pop -= 1
    if self.tutorial_confirm_timer > 0:
        self.tutorial_confirm_timer -= 1
    if self.tutorial_pause_overlay > 0:
        self.tutorial_pause_overlay -= 1
    for p in self.particles:
        p.update()
    self.particles = [p for p in self.particles if p.alive]
Game.update_tutorial = _tutorial_v6_update
def _draw_training_key(x, y, key, active=False, done=False):
    col = C3 if done else (C14 if active else C5)
    pyxel.rect(x, y, 25, 15, C1)
    pyxel.rectb(x, y, 25, 15, col)
    pyxel.text(x + 5, y + 5, key, col)
def _tutorial_v6_draw(self):
    _ensure_tutorial_v6(self)
    pyxel.cls(C0)
    pyxel.rect(0, 0, SCREEN_W, SCREEN_H, C0)
    for x in range(0, SCREEN_W, 16):
        pyxel.line(x, 0, x - 72, SCREEN_H, C1)
    pyxel.rect(12, 12, 296, 176, C0)
    pyxel.rectb(12, 12, 296, 176, C12)
    pyxel.text(96, 20, 'INTERACTIVE TRAINING', C14)
    pyxel.text(232, 20, 'ENTER SKIP', C6)
    pyxel.rect(24, 149, 272, 20, C3)
    pyxel.line(24, 149, 296, 149, C7)
    for x in range(30, 292, 28):
        pyxel.rect(x, 151, 12, 3, C11)
    pyxel.rect(38, 132, 40, 17, C4); pyxel.rectb(38,132,40,17,C7)
    pyxel.rect(153, 134, 32, 15, C5); pyxel.rectb(153,134,32,15,C7)
    if self.tutorial_dash_timer > 0:
        for i in range(5):
            px = int(self.tutorial_fake_x - 10 - i * 10)
            pyxel.line(px, int(self.tutorial_fake_y + 4 + i % 2), px + 13, int(self.tutorial_fake_y + 4 + i % 2), C10 if i < 2 else C7)
    _draw_skin_preview(int(self.tutorial_fake_x), int(self.tutorial_fake_y - 9), self.skin_index, self.current_country()['key'], empire=False)
    bx, by = 236, 83
    pyxel.line(bx, by + 13, bx, 142, C7)
    if self.tutorial_balloon_hp > 0:
        pyxel.circ(bx, by, 14 + (1 if self.tutorial_balloon_pop else 0), C8)
        pyxel.circb(bx, by, 14, C7)
        pyxel.text(bx - 4, by - 3, str(self.tutorial_balloon_hp), C7)
    else:
        pyxel.circb(bx, by, 18, C10)
        pyxel.text(bx - 12, by - 3, 'POP', C10)
    pyxel.text(bx - 19, by - 25, 'BALLOON TARGET', C10)
    for sx, sy, vx, vy, life in self.tutorial_shots:
        pyxel.circ(int(sx), int(sy), 2, C10)
    steps = [
        ('1  JUMP', 'PRESS W', 'Press W once. Watch the player jump.'),
        ('2  MOVE', 'HOLD A OR D', 'Move left/right until this lesson completes.'),
        ('3  SHOOT', 'ARROWS', 'Use arrow keys to shoot the balloon.'),
        ('4  DASH', 'SPACE', 'Press SPACE for a short dash.'),
        ('5  SPECIALS', 'J / K / SHIFT', 'J bomb, K rocket, SHIFT weapon switch.'),
        ('6  PAUSE', 'P', 'Press P to see pause control.'),
        ('READY', 'ENTER', 'Training complete. Press ENTER to start.'),
    ]
    idx = min(self.tutorial_step, 6)
    title, keys, desc = steps[idx]
    pyxel.rect(28, 42, 264, 42, C1)
    pyxel.rectb(28, 42, 264, 42, C10 if self.tutorial_confirm_timer > 0 else C14)
    pyxel.text(42, 52, title, C14)
    pyxel.text(142, 52, keys, C10)
    pyxel.text(42, 68, desc[:45], C7)
    row = [('W',0), ('A/D',1), ('ARW',2), ('SPC',3), ('J/K',4), ('P',5)]
    for i, (key, si) in enumerate(row):
        _draw_training_key(44 + i * 42, 101, key, active=(self.tutorial_step == si), done=(self.tutorial_step > si))
    pyxel.text(42, 122, 'Each lesson waits for the real input. No auto-complete.', C6)
    if self.tutorial_special_seen:
        pyxel.text(42, 134, self.tutorial_special_seen, C10)
    if self.tutorial_pause_overlay > 0:
        pyxel.rect(104, 88, 112, 38, C0)
        pyxel.rectb(104, 88, 112, 38, C14)
        pyxel.text(146, 98, 'PAUSE', C14)
        pyxel.text(122, 112, 'P toggles pause in-game', C7)
    if self.tutorial_step >= 6:
        pyxel.rect(76, 173, 168, 10, C10)
        pyxel.text(106, 175, 'TRAINING DONE - ENTER', C0)
    for p in self.particles:
        p.draw(0)
Game.draw_tutorial = _tutorial_v6_draw
def _ooo_dev_toggle_v6(self):
    buf = getattr(self, 'dev_buffer', '')
    if pyxel.btnp(pyxel.KEY_O):
        buf = (buf + 'O')[-3:]
        self.dev_buffer = buf
        if buf == 'OOO':
            self.dev_mode = not getattr(self, 'dev_mode', False)
            self.dev_panel_open = self.dev_mode
            self.secret_route_unlocked = True
            self.secret_wall_broken = True
            self.dev_buffer = ''
            self.add_text(self.player.x + 10, 28, 'DEV MODE' if self.dev_mode else 'DEV OFF', C14 if self.dev_mode else C8)
            sfx(1, 4)
Game.update_dev_buffer = _ooo_dev_toggle_v6
def _clear_for_new_mp_turn(game):
    game.player_bullets.clear(); game.enemy_bullets.clear(); game.rockets.clear(); game.bombs.clear()
    game.enemies.clear(); game.effects.clear(); game.particles.clear(); game.texts.clear(); game.powerups.clear(); game.coins.clear()
    if hasattr(game, 'supply_drops'):
        game.supply_drops.clear()
    game.terrain.reset()
    game.cam_x = 0
    _v6_disable_final_bunker(game)
    game.level_boss_defeated = False
    game.level_boss_spawned = False
    game.boss_complete_pending = False
    game.boss_death_timer = 0
def _reset_multiplayer_session_v6(self):
    self.mp_player_count = int(clamp(getattr(self, 'mp_player_count', 2), 2, 8))
    self.mp_scores = [0 for _ in range(self.mp_player_count)]
    self.mp_current_player = 0
    self.mp_segment_progress = 0
    self.mp_campaign_segments_left = CAMPAIGN_LEVELS
    self.level = 1
    self.distance = 0
    self.total_distance = 0
    self.checkpoint_x = 40
    self.checkpoint_level = 1
    self.survival_frames = 0
    old_player = self.player
    self.player = Player(self.terrain, self.skin_index, self.country_index, self.extra_hp_bonus)
    self.player.fire_rate_bonus = getattr(old_player, 'fire_rate_bonus', 0)
    self.player.reset(40, self.difficulty_value())
    self.player.max_hp_bonus = max(self.player.max_hp_bonus, self.extra_hp_bonus)
    self.player.max_hp = 5 + self.player.max_hp_bonus
    self.player.hp = self.player.max_hp
    _clear_for_new_mp_turn(self)
    self.state = 'playing'
    self.add_text(self.player.x + 10, 24, 'PLAYER 1', C14)
    stop_story_music()
Game.reset_multiplayer_session = _reset_multiplayer_session_v6
_old_v6_menu_update = Game.update_menu
def _update_menu_v6(self):
    before_state = getattr(self, 'state', '')
    if pyxel.btnp(pyxel.KEY_RETURN) and self.is_multiplayer() and self.skin_index in self.unlocked_skins:
        self.player.skin_index = self.skin_index
        self.player.country_index = self.country_index
        self.reset_multiplayer_session()
        return
    return _old_v6_menu_update(self)
Game.update_menu = _update_menu_v6
_old_v6_update_playing = Game.update_playing
def _update_playing_v6(self):
    _v6_disable_final_bunker(self)
    if self.is_multiplayer():
        self.enemies = [e for e in self.enemies if not isinstance(e, BossEnemy)]
        self.level_boss_defeated = False
        self.level_boss_spawned = False
        self.boss_complete_pending = False
        self.boss_death_timer = 0
        _old_update_playing_before_v5(self)
        _v6_disable_final_bunker(self)
        self.enemies = [e for e in self.enemies if not isinstance(e, BossEnemy)]
        if self.state == 'story_update' and self.mp_mode_names[self.mp_mode_index] == 'CAMPAIGN ROTATION':
            _clear_for_new_mp_turn(self)
            self.player.reset(40, self.difficulty_value())
            self.player.max_hp_bonus = max(self.player.max_hp_bonus, self.extra_hp_bonus)
            self.player.max_hp = 5 + self.player.max_hp_bonus
            self.player.hp = self.player.max_hp
            self.cam_x = 0
            self.state = 'playing'
            self.add_text(self.player.x + 10, 24, 'PLAYER ' + str(self.mp_current_player + 1), C14)
            stop_story_music()
        return
    _old_v6_update_playing(self)
    _v6_disable_final_bunker(self)
Game.update_playing = _update_playing_v6
_old_v6_respawn = Game.respawn_or_game_over
def _respawn_or_game_over_v6(self):
    if not self.is_multiplayer():
        return _old_v6_respawn(self)
    if not self.mp_scores or len(self.mp_scores) != self.mp_player_count:
        self.mp_scores = [0 for _ in range(self.mp_player_count)]
    self.mp_scores[self.mp_current_player] += max(0, self.player.score)
    self.mp_current_player = (self.mp_current_player + 1) % self.mp_player_count
    _clear_for_new_mp_turn(self)
    spawn_x = 40 if self.mp_mode_names[self.mp_mode_index] == 'SURVIVAL ROTATION' else 40
    self.player.reset(spawn_x, self.difficulty_value())
    self.player.max_hp_bonus = max(self.player.max_hp_bonus, self.extra_hp_bonus)
    self.player.max_hp = 5 + self.player.max_hp_bonus
    self.player.hp = self.player.max_hp
    self.distance = 0 if self.mp_mode_names[self.mp_mode_index] == 'SURVIVAL ROTATION' else self.distance
    self.mp_segment_progress = 0
    self.cam_x = 0
    self.state = 'playing'
    self.add_text(self.player.x + 10, 24, 'PLAYER ' + str(self.mp_current_player + 1), C14)
Game.respawn_or_game_over = _respawn_or_game_over_v6
_old_v6_draw_hud = Game.draw_hud
def _draw_hud_v6(self):
    _old_v6_draw_hud(self)
    if self.is_multiplayer() and self.state in ('playing', 'paused'):
        pyxel.rect(214, 26, 100, 15 + self.mp_player_count * 8, C0)
        pyxel.rectb(214, 26, 100, 15 + self.mp_player_count * 8, C12)
        pyxel.text(220, 31, 'MULTIPLAYER', C14)
        for i in range(self.mp_player_count):
            col = C10 if i == self.mp_current_player else C7
            pyxel.text(220, 43 + i * 8, 'P' + str(i+1) + '  ' + str(self.mp_scores[i] if i < len(self.mp_scores) else 0), col)
Game.draw_hud = _draw_hud_v6
def _draw_bunkers_v7(self):
    if not (self.is_campaign() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION")):
        return
    bx = self.checkpoint_x - 28
    gy = self.terrain.ground_y_at(bx + 14, self.difficulty_value())
    sx = int(bx - self.cam_x)
    if -60 <= sx <= SCREEN_W + 60:
        self.draw_bunker_style(bx, gy, self.current_country()["key"])
    level_len = 1500 if (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION") else LEVEL_DISTANCE
    finish_x = self.checkpoint_x + level_len + 22
    remaining = max(0, finish_x - (self.player.x + PLAYER_W / 2))
    if remaining < 430:
        gy2 = self.terrain.ground_y_at(finish_x + 18, self.difficulty_value())
        sx2 = int(finish_x - self.cam_x)
        if -80 <= sx2 <= SCREEN_W + 80:
            self.draw_bunker_style(finish_x, gy2, self.current_country()["key"])
            pyxel.text(sx2 + 2, gy2 - 46, "LEVEL BUNKER", C10)
            if remaining < 90:
                pyxel.text(sx2 - 10, gy2 - 57, "FINISH", C14)
Game.draw_bunkers = _draw_bunkers_v7
def _ensure_tutorial_v7(self):
    if getattr(self, "tutorial_v7_ready", False):
        return
    self.tutorial_v7_ready = True
    self.tutorial_step = 0
    self.tutorial_fake_x = 56
    self.tutorial_fake_y = 131
    self.tutorial_fake_vy = 0
    self.tutorial_fake_on_ground = True
    self.tutorial_fake_face = 1
    self.tutorial_move_meter = 0
    self.tutorial_dash_timer = 0
    self.tutorial_shots = []
    self.tutorial_balloon_hp = 3
    self.tutorial_balloon_pop = 0
    self.tutorial_confirm_timer = 0
    self.tutorial_special_seen = ""
    self.tutorial_pause_overlay = 0
    self.tutorial_used = set()
def _tutorial_v7_complete_step(self, step=None):
    if step is None:
        step = self.tutorial_step
    if self.tutorial_step == step:
        self.tutorial_step = min(6, self.tutorial_step + 1)
        self.tutorial_confirm_timer = 34
        for _ in range(10):
            self.particles.append(Particle(self.tutorial_fake_x + 8, self.tutorial_fake_y + 4, random.uniform(-1.6,1.6), random.uniform(-2.2,-.2), random.choice([C10,C14,C7]), random.randint(16,32), size=1, gravity=.05))
        sfx(0, 3)
def _tutorial_v7_update(self):
    _ensure_tutorial_v7(self)
    if pyxel.btnp(pyxel.KEY_RETURN):
        self.state = "playing"
        stop_story_music()
        return
    moving = False
    if pyxel.btn(pyxel.KEY_A):
        self.tutorial_fake_x -= 1.7
        self.tutorial_fake_face = -1
        self.tutorial_move_meter += 1
        self.tutorial_used.add("MOVE")
        moving = True
    if pyxel.btn(pyxel.KEY_D):
        self.tutorial_fake_x += 1.7
        self.tutorial_fake_face = 1
        self.tutorial_move_meter += 1
        self.tutorial_used.add("MOVE")
        moving = True
    self.tutorial_fake_x = clamp(self.tutorial_fake_x, 36, 264)
    if pyxel.btnp(pyxel.KEY_W) and self.tutorial_fake_on_ground:
        self.tutorial_fake_vy = -5.4
        self.tutorial_fake_on_ground = False
        self.tutorial_used.add("JUMP")
        if self.tutorial_step == 0:
            _tutorial_v7_complete_step(self, 0)
    self.tutorial_fake_vy += 0.28
    self.tutorial_fake_y += self.tutorial_fake_vy
    if self.tutorial_fake_y >= 131:
        self.tutorial_fake_y = 131
        self.tutorial_fake_vy = 0
        self.tutorial_fake_on_ground = True
    if pyxel.btnp(pyxel.KEY_SPACE):
        self.tutorial_dash_timer = 22
        self.tutorial_fake_x = clamp(self.tutorial_fake_x + 36 * self.tutorial_fake_face, 36, 264)
        self.tutorial_used.add("DASH")
        if self.tutorial_step == 3:
            _tutorial_v7_complete_step(self, 3)
    if self.tutorial_dash_timer > 0:
        self.tutorial_dash_timer -= 1
    sx = self.tutorial_fake_x + 12
    sy = self.tutorial_fake_y - 2
    shot = None
    if pyxel.btnp(pyxel.KEY_RIGHT):
        shot = [sx, sy, 4.1, 0, 52]
        self.tutorial_fake_face = 1
    elif pyxel.btnp(pyxel.KEY_LEFT):
        shot = [sx, sy, -4.1, 0, 52]
        self.tutorial_fake_face = -1
    elif pyxel.btnp(pyxel.KEY_UP):
        shot = [sx, sy, 0, -4.1, 52]
    elif pyxel.btnp(pyxel.KEY_DOWN):
        shot = [sx, sy, 0, 4.1, 52]
    if shot:
        self.tutorial_shots.append(shot)
        self.tutorial_used.add("SHOOT")
        sfx(0, 0)
    bx, by = 236, 83
    newshots = []
    for b in self.tutorial_shots:
        b[0] += b[2]; b[1] += b[3]; b[4] -= 1
        if self.tutorial_balloon_hp > 0 and (b[0]-bx)**2 + (b[1]-by)**2 < 18*18:
            self.tutorial_balloon_hp -= 1
            self.tutorial_balloon_pop = 14
            for _ in range(8):
                self.particles.append(Particle(bx, by, random.uniform(-1.8,1.8), random.uniform(-2,1), random.choice([C8,C10,C14,C7]), random.randint(18,34), size=1, gravity=.04))
            if self.tutorial_balloon_hp <= 0 and self.tutorial_step == 2:
                _tutorial_v7_complete_step(self, 2)
            continue
        if b[4] > 0 and -10 <= b[0] <= SCREEN_W+10 and -10 <= b[1] <= SCREEN_H+10:
            newshots.append(b)
    self.tutorial_shots = newshots
    if self.tutorial_balloon_pop > 0:
        self.tutorial_balloon_pop -= 1
    if pyxel.btnp(pyxel.KEY_J):
        self.tutorial_special_seen = "BOMB TESTED"
        self.tutorial_used.add("SPECIAL")
        if self.tutorial_step == 4:
            _tutorial_v7_complete_step(self, 4)
    if pyxel.btnp(pyxel.KEY_K):
        self.tutorial_special_seen = "ROCKET TESTED"
        self.tutorial_used.add("SPECIAL")
        if self.tutorial_step == 4:
            _tutorial_v7_complete_step(self, 4)
    if pyxel.btnp(pyxel.KEY_SHIFT):
        self.tutorial_special_seen = "WEAPON SWITCH TESTED"
        self.tutorial_used.add("SPECIAL")
        if self.tutorial_step == 4:
            _tutorial_v7_complete_step(self, 4)
    if pyxel.btnp(pyxel.KEY_P):
        self.tutorial_pause_overlay = 48
        self.tutorial_used.add("PAUSE")
        if self.tutorial_step == 5:
            _tutorial_v7_complete_step(self, 5)
        sfx(1, 4)
    if self.tutorial_step == 1 and self.tutorial_move_meter >= 24:
        _tutorial_v7_complete_step(self, 1)
    if self.tutorial_step == 2 and self.tutorial_balloon_hp <= 0:
        _tutorial_v7_complete_step(self, 2)
    if self.tutorial_confirm_timer > 0:
        self.tutorial_confirm_timer -= 1
    if self.tutorial_pause_overlay > 0:
        self.tutorial_pause_overlay -= 1
    for p in self.particles:
        p.update()
    self.particles = [p for p in self.particles if p.alive]
Game.update_tutorial = _tutorial_v7_update
def _tutorial_v7_draw(self):
    _ensure_tutorial_v7(self)
    pyxel.cls(C0)
    for x in range(0, SCREEN_W, 16):
        pyxel.line(x, 0, x - 70, SCREEN_H, C1)
    pyxel.rect(10, 10, 300, 178, C0)
    pyxel.rectb(10, 10, 300, 178, C12)
    pyxel.text(86, 18, "FREE TRAINING ROOM", C14)
    pyxel.text(240, 18, "ENTER SKIP", C6)
    pyxel.rect(24, 149, 272, 20, C3)
    pyxel.line(24, 149, 296, 149, C7)
    for x in range(30, 292, 26):
        pyxel.rect(x, 151, 11, 3, C11)
    pyxel.rectb(25, 36, 270, 112, C5)
    pyxel.text(34, 38, "All controls work anytime. The highlighted lesson is just the next goal.", C6)
    if self.tutorial_dash_timer > 0:
        for i in range(4):
            px = int(self.tutorial_fake_x - self.tutorial_fake_face * (10 + i*10))
            pyxel.line(px, int(self.tutorial_fake_y + 4), px - self.tutorial_fake_face*10, int(self.tutorial_fake_y + 4), C10 if i < 2 else C7)
    _draw_skin_preview(int(self.tutorial_fake_x), int(self.tutorial_fake_y - 9), self.skin_index, self.current_country()["key"], empire=False)
    bx, by = 236, 83
    pyxel.line(bx, by + 13, bx, 142, C7)
    if self.tutorial_balloon_hp > 0:
        pyxel.circ(bx, by, 14 + (1 if self.tutorial_balloon_pop else 0), C8)
        pyxel.circb(bx, by, 14, C7)
        pyxel.text(bx - 4, by - 3, str(self.tutorial_balloon_hp), C7)
    else:
        pyxel.circb(bx, by, 18, C10)
        pyxel.text(bx - 12, by - 3, "POP", C10)
    pyxel.text(bx - 20, by - 25, "BALLOON", C10)
    for sx, sy, vx, vy, life in self.tutorial_shots:
        pyxel.circ(int(sx), int(sy), 2, C10)
    steps = [
        ("JUMP", "Press W once"),
        ("MOVE", "Hold A or D"),
        ("SHOOT", "Arrow keys: pop balloon"),
        ("DASH", "Press SPACE"),
        ("SPECIAL", "Press J, K, or SHIFT"),
        ("PAUSE", "Press P"),
        ("READY", "Press ENTER to start"),
    ]
    idx = min(self.tutorial_step, 6)
    title, desc = steps[idx]
    pyxel.rect(28, 52, 160, 33, C1)
    pyxel.rectb(28, 52, 160, 33, C10 if self.tutorial_confirm_timer > 0 else C14)
    pyxel.text(38, 61, "NEXT: " + title, C14)
    pyxel.text(38, 73, desc[:30], C7)
    row = [("W",0), ("A/D",1), ("ARW",2), ("SPC",3), ("J/K/SHIFT",4), ("P",5)]
    x = 30
    for key, si in row:
        w = 25 if len(key) <= 3 else 44
        active = self.tutorial_step == si
        done = self.tutorial_step > si
        col = C3 if done else (C14 if active else C5)
        pyxel.rect(x, 103, w, 15, C1)
        pyxel.rectb(x, 103, w, 15, col)
        pyxel.text(x + 4, 108, key, col)
        x += w + 7
    used = "USED: " + ", ".join(sorted(self.tutorial_used)) if self.tutorial_used else "TRY ANY CONTROL NOW"
    pyxel.text(34, 126, used[:45], C10 if self.tutorial_used else C6)
    if self.tutorial_special_seen:
        pyxel.text(34, 137, self.tutorial_special_seen, C10)
    if self.tutorial_pause_overlay > 0:
        pyxel.rect(104, 88, 112, 38, C0)
        pyxel.rectb(104, 88, 112, 38, C14)
        pyxel.text(146, 98, "PAUSE", C14)
        pyxel.text(122, 112, "P pauses in-game", C7)
    if self.tutorial_step >= 6:
        pyxel.rect(74, 172, 172, 11, C10)
        pyxel.text(103, 175, "TRAINING DONE - ENTER", C0)
    for p in self.particles:
        p.draw(0)
Game.draw_tutorial = _tutorial_v7_draw
def _draw_hud_v7(self):
    pyxel.rect(0, 0, SCREEN_W, 24, C0)
    pyxel.rectb(0, 0, SCREEN_W, 24, C5)
    pyxel.text(6, 5, "HP", C7)
    max_hp = max(1, min(12, int(self.player.max_hp)))
    for i in range(max_hp):
        col = C8 if i < self.player.hp else C5
        pyxel.rect(22 + i * 9, 5, 7, 6, col)
    pyxel.text(6, 15, "$" + str(self.coins_total), C10)
    pyxel.text(68, 5, "WPN " + self.player.weapon[:7], C7)
    pyxel.text(150, 5, "R" + str(self.player.rockets), C10)
    pyxel.text(184, 5, "B" + str(self.player.bombs), C8)
    pyxel.text(220, 5, "SCORE " + str(self.player.score), C14)
    if self.dev_mode:
        pyxel.text(270, 15, "DEV", C14)
    draw_flag(self.current_country()["key"], 296, 13, 14, 8, pyxel.frame_count * 0.2)
    y = 28
    buffs = []
    if self.player.rapid_timer > 0: buffs.append("RAPID " + str(self.player.rapid_timer // 60) + "s")
    if self.player.spread_timer > 0: buffs.append("SPREAD " + str(self.player.spread_timer // 60) + "s")
    if self.player.overdrive_timer > 0: buffs.append("OVER " + str(self.player.overdrive_timer // 60) + "s")
    if self.player.invuln > 90: buffs.append("SHIELD " + str(self.player.invuln // 60) + "s")
    for b in buffs[:4]:
        pyxel.text(6, y, b, C10)
        y += 8
    if self.player.combo > 1:
        pyxel.text(6, 166, "COMBO x" + str(self.player.combo), C10)
    if self.is_survival() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "SURVIVAL ROTATION"):
        pyxel.rect(118, 29, 84, 17, C0)
        pyxel.rectb(118, 29, 84, 17, C7)
        pyxel.text(130, 35, "TIME " + str(self.survival_frames // 60), C14)
    elif self.is_campaign() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"):
        level_max = EMPIRE_CAMPAIGN_LEVELS if getattr(self, "empire_mode", False) else CAMPAIGN_LEVELS
        level_len = 1500 if (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION") else LEVEL_DISTANCE
        bx, by, bw = 86, 171, 166
        fill = int(bw * clamp(self.distance / max(1, level_len), 0, 1))
        pyxel.rect(80, 163, 188, 28, C0)
        pyxel.rectb(80, 163, 188, 28, C7)
        pyxel.text(88, 166, "LV " + str(self.level) + "/" + str(level_max), C7)
        pyxel.text(196, 166, str(int(max(0, level_len - self.distance))) + "m", C7)
        pyxel.rect(bx, by + 9, bw, 6, C5)
        pyxel.rect(bx, by + 9, fill, 6, C13)
        pyxel.rectb(bx, by + 9, bw, 6, C7)
    else:
        pyxel.rect(186, 164, 124, 28, C0)
        pyxel.rectb(186, 164, 124, 28, C12)
        pyxel.text(194, 170, "MP P" + str(self.mp_current_player + 1), C14)
        pyxel.text(194, 181, self.mp_mode_names[self.mp_mode_index][:16], C10)
    if self.is_multiplayer() and self.state in ("playing", "paused"):
        h = 15 + self.mp_player_count * 8
        pyxel.rect(214, 27, 100, h, C0)
        pyxel.rectb(214, 27, 100, h, C12)
        pyxel.text(220, 32, "MULTIPLAYER", C14)
        for i in range(self.mp_player_count):
            col = C10 if i == self.mp_current_player else C7
            score = self.mp_scores[i] if i < len(self.mp_scores) else 0
            pyxel.text(220, 44 + i * 8, "P" + str(i+1) + " " + str(score), col)
Game.draw_hud = _draw_hud_v7
_old_v7_update = Game.update
def _update_v7(self):
    self.final_bunker_active = False
    self.final_bunker_ready = False
    self.final_bunker_x = -999999
    return _old_v7_update(self)
Game.update = _update_v7
_old_v7_update_playing = Game.update_playing
def _update_playing_v7(self):
    self.final_bunker_active = False
    self.final_bunker_ready = False
    self.final_bunker_x = -999999
    _old_v7_update_playing(self)
    self.final_bunker_active = False
    self.final_bunker_ready = False
    self.final_bunker_x = -999999
Game.update_playing = _update_playing_v7
_old_v7_draw_world = Game.draw_world
def _draw_world_v7(self):
    self.final_bunker_active = False
    self.final_bunker_ready = False
    self.final_bunker_x = -999999
    _old_v7_draw_world(self)
    self.final_bunker_active = False
    self.final_bunker_ready = False
    self.final_bunker_x = -999999
Game.draw_world = _draw_world_v7
def _v8_toggle_dev(self):
    try:
        _ensure_creative_state(self)
    except Exception:
        pass
    try:
        _ensure_secret_bunker_state(self)
        self.secret_wall_broken = True
        self.secret_route_unlocked = True
    except Exception:
        pass
    self.dev_mode = not getattr(self, 'dev_mode', False)
    self.dev_panel_open = self.dev_mode
    self.dev_buffer = ''
    try:
        sfx(1, 4)
    except Exception:
        pass
def _ooo_v8_update_dev_buffer(self):
    if getattr(self, '_dev_buffer_frame_v8', -1) == pyxel.frame_count:
        return
    self._dev_buffer_frame_v8 = pyxel.frame_count
    if not hasattr(self, 'dev_buffer'):
        self.dev_buffer = ''
    if pyxel.btnp(pyxel.KEY_O):
        self.dev_buffer = (self.dev_buffer + 'O')[-3:]
        if self.dev_buffer == 'OOO':
            _v8_toggle_dev(self)
Game.update_dev_buffer = _ooo_v8_update_dev_buffer
_old_v8_update = Game.update
def _update_v8(self):
    try:
        self.update_dev_buffer()
    except Exception:
        pass
    return _old_v8_update(self)
Game.update = _update_v8
_old_v8_reset_run = Game.reset_run
def _reset_run_v8(self):
    was_dev = getattr(self, 'dev_mode', False)
    was_panel = getattr(self, 'dev_panel_open', False)
    _old_v8_reset_run(self)
    if was_dev:
        self.dev_mode = True
        self.dev_panel_open = was_panel or True
Game.reset_run = _reset_run_v8
def _draw_dev_panel_v8(game):
    try:
        _ensure_creative_state(game)
    except Exception:
        pass
    try:
        _ensure_empire_state(game)
    except Exception:
        pass
    if not getattr(game, 'dev_mode', False) or not getattr(game, 'dev_panel_open', False):
        return
    try:
        rows = _dev_rows(game)
    except Exception:
        rows = []
    x, y, w, h = 28, 18, 264, 164
    pulse = C14 if (pyxel.frame_count // 7) % 2 == 0 else C12
    pyxel.rect(x, y, w, h, C0)
    pyxel.rectb(x, y, w, h, pulse)
    pyxel.rect(x + 4, y + 4, w - 8, 15, C1)
    pyxel.text(x + 10, y + 8, 'OOO DEV CONTROL PANEL', C14)
    pyxel.text(x + 176, y + 8, 'OOO = CLOSE', C6)
    hx, hy = x + 8, y + 28
    pyxel.rect(hx, hy, 84, 100, C1)
    pyxel.rectb(hx, hy, 84, 100, C5)
    help_lines = [
        ('CONTROLS', C14),
        ('MOUSE: DRAG', C7),
        ('UP/DOWN ROW', C7),
        ('LEFT/RIGHT SET', C7),
        ('SHIFT = FAST', C7),
        ('S SUPPLY', C10),
        ('B BOSS', C8),
        ('C CLEAR', C7),
        ('R RESET', C6),
        ('SPACE HIDE', C6),
    ]
    for i, (line, col) in enumerate(help_lines):
        pyxel.text(hx + 5, hy + 6 + i * 9, line, col)
    bx, by = x + 100, y + 28
    pyxel.rect(bx, by, 152, 120, C1)
    pyxel.rectb(bx, by, 152, 120, C12)
    pyxel.text(bx + 9, by + 7, 'LIVE SLIDERS', C14)
    if not rows:
        pyxel.text(bx + 12, by + 38, 'NO ROWS LOADED', C8)
    else:
        cursor = int(getattr(game, 'dev_cursor', 0)) % max(1, len(rows))
        game.dev_cursor = cursor
        start = max(0, min(cursor - 4, max(0, len(rows) - 8)))
        bar_x, bar_w = bx + 61, 76
        for i in range(start, min(start + 8, len(rows))):
            label, value, lo, hi, step, apply = rows[i]
            row_y = by + 24 + (i - start) * 12
            selected = i == cursor
            if selected:
                pyxel.rect(bx + 5, row_y - 3, 142, 10, C0)
                pyxel.rectb(bx + 5, row_y - 3, 142, 10, C14)
            pyxel.text(bx + 9, row_y, ('PICK LV' if label == 'LEVEL' else str(label)[:8]), C14 if selected else C7)
            pyxel.text(bx + 48, row_y, str(int(value)), C10 if selected else C6)
            ratio = 0 if hi == lo else clamp((value - lo) / (hi - lo), 0, 1)
            pyxel.rect(bar_x, row_y + 2, bar_w, 3, C5)
            pyxel.rect(bar_x, row_y + 2, int(bar_w * ratio), 3, C10 if label != 'EMPIRE' else C14)
            knob_x = bar_x + int(bar_w * ratio)
            pyxel.rect(knob_x - 2, row_y - 2, 5, 8, C14 if selected else C7)
    pyxel.rect(x + 8, y + h - 22, w - 16, 14, C1)
    pyxel.rectb(x + 8, y + h - 22, w - 16, 14, C5)
    pyxel.text(x + 14, y + h - 18, 'DRAG SLIDERS OR USE KEYS - DEV IS ACTIVE', C10)
globals()['_draw_dev_panel'] = _draw_dev_panel_v8
_old_v8_draw_hud = Game.draw_hud
def _draw_hud_v8(self):
    _old_v8_draw_hud(self)
    _draw_dev_panel_v8(self)
Game.draw_hud = _draw_hud_v8
_old_v8_update_playing = Game.update_playing
def _update_playing_v8(self):
    if getattr(self, 'dev_mode', False) and getattr(self, 'dev_panel_open', False):
        try:
            self.update_dev_buffer()
        except Exception:
            pass
        try:
            _dev_update_panel(self)
        except Exception:
            pass
        try:
            self.update_shake()
        except Exception:
            pass
        return
    return _old_v8_update_playing(self)
Game.update_playing = _update_playing_v8
EMPIRE_SPEED_BONUS_SMALL = 0.16
EMPIRE_AIR_BONUS_SMALL = 0.12
EMPIRE_JUMP_BONUS_SMALL = 0.22
EMPIRE_TOTAL_LEVELS = CAMPAIGN_LEVELS + EMPIRE_CAMPAIGN_LEVELS
REAL_EMPIRE_NAMES["usa"] = "AMERICAN FEDERAL EMPIRE"
EMPIRE_NAMES["usa"] = "AMERICAN FEDERAL EMPIRE"
if "usa" in MAJOR_EMPIRE_DATA:
    MAJOR_EMPIRE_DATA["usa"].update({
        "empire": "AMERICAN FEDERAL EMPIRE",
        "power": "FEDERAL CARRIER COMMAND",
        "growth": "UNIFIED STATES IMPERIUM",
        "icon": "EAGLE STAR COMMAND",
        "territory": "COAST-TO-COAST FEDERAL EXPANSION",
    })
def _better_empire_name_v9(game):
    return REAL_EMPIRE_NAMES.get(game.current_country()["key"], game.current_country()["name"] + " EMPIRE")
globals()["_better_empire_name"] = _better_empire_name_v9
globals()["_empire_name"] = _better_empire_name_v9
def _ensure_v9_state(game):
    if not hasattr(game, "empire_base_speed"):
        game.empire_base_speed = getattr(game.player, "speed", 1.8)
    if not hasattr(game, "empire_base_air_speed"):
        game.empire_base_air_speed = getattr(game.player, "air_speed", 1.5)
    if not hasattr(game, "empire_base_jump_power"):
        game.empire_base_jump_power = getattr(game.player, "jump_power", -5.5)
    if not hasattr(game, "empire_difficulty_offset"):
        game.empire_difficulty_offset = 0
_old_v9_difficulty_value = Game.difficulty_value
def _difficulty_value_v9(self):
    if getattr(self, "empire_mode", False) and self.is_campaign():
        return self.level + self.progress_ratio() * 2.35
    return _old_v9_difficulty_value(self)
Game.difficulty_value = _difficulty_value_v9
_old_v9_danger_scale = Game.danger_scale
def _danger_scale_v9(self):
    if getattr(self, "empire_mode", False) and self.is_campaign():
        return 1 + (self.level - 1) * 0.24 + self.progress_ratio() * 1.35
    return _old_v9_danger_scale(self)
Game.danger_scale = _danger_scale_v9
def _apply_empire_bonuses_v9(game):
    if not getattr(game, "empire_mode", False):
        return
    _ensure_v9_state(game)
    base_speed = getattr(game, "empire_base_speed", 1.8)
    base_air = getattr(game, "empire_base_air_speed", 1.5)
    base_jump = getattr(game, "empire_base_jump_power", -5.5)
    game.player.speed = base_speed + EMPIRE_SPEED_BONUS_SMALL
    game.player.air_speed = base_air + EMPIRE_AIR_BONUS_SMALL
    game.player.jump_power = base_jump - EMPIRE_JUMP_BONUS_SMALL
    game.player.max_hp = 1
    game.player.hp = min(game.player.hp, 1)
globals()["_apply_empire_bonuses"] = _apply_empire_bonuses_v9
def _clear_level_objects_v9(game):
    for name in ["player_bullets", "enemy_bullets", "rockets", "bombs", "enemies", "effects", "particles", "texts", "powerups", "coins"]:
        try:
            getattr(game, name).clear()
        except Exception:
            pass
    if hasattr(game, "supply_drops"):
        game.supply_drops.clear()
    game.field_order = None
    game.field_order_progress = 0
    game.field_order_timer = 0
    game.field_order_cooldown = min(getattr(game, "field_order_cooldown", 4 * FPS), 4 * FPS)
def _enter_empire_campaign_v9(game, from_dev=False):
    _ensure_empire_state(game)
    _ensure_v9_state(game)
    game.empire_mode = True
    game.empire_won = False
    game.empire_difficulty_offset = CAMPAIGN_LEVELS
    game.level = CAMPAIGN_LEVELS + 1
    game.distance = 0
    game.checkpoint_level = game.level
    game.checkpoint_x = 40
    game.terrain.reset()
    game.player.reset(40, game.difficulty_value())
    _apply_empire_bonuses_v9(game)
    game.player.hp = 1
    game.player.invuln = max(game.player.invuln, FPS)
    _clear_level_objects_v9(game)
    game.cam_x = 0
    game.empire_powerup_notice = FPS * 5
    game.state = "playing" if from_dev else "story_update"
    game.intro_timer = 0
    stop_story_music()
globals()["_enter_empire_campaign"] = _enter_empire_campaign_v9
def _next_level_v9(self):
    _ensure_empire_state(self)
    if not getattr(self, "empire_mode", False):
        if self.level >= CAMPAIGN_LEVELS:
            try:
                _start_empire_ascension(self)
            except Exception:
                _enter_empire_campaign_v9(self, from_dev=False)
            self.story_progress_index += 1
            return
        return _old_empire_next_level(self)
    self.level += 1
    self.distance = 0
    self.checkpoint_level = self.level
    self.checkpoint_x = 40
    self.terrain.reset()
    _clear_level_objects_v9(self)
    if self.level > EMPIRE_TOTAL_LEVELS:
        self.empire_won = True
        self.state = "victory"
        play_story_music()
    else:
        self.player.reset(self.checkpoint_x, self.difficulty_value())
        _apply_empire_bonuses_v9(self)
        self.player.hp = 1
        self.story_progress_index += 1
        self.state = "story_update"
        self.intro_timer = 0
        play_story_music()
Game.next_level = _next_level_v9
def _dev_set_level_v9(game, level):
    _ensure_empire_state(game)
    level = int(clamp(int(level), 1, EMPIRE_TOTAL_LEVELS))
    game.empire_mode = level > CAMPAIGN_LEVELS
    game.empire_won = False
    game.level = level
    game.checkpoint_level = level
    game.distance = 0
    game.checkpoint_x = 40
    game.terrain.reset()
    game.player.reset(40, game.difficulty_value())
    if game.empire_mode:
        _apply_empire_bonuses_v9(game)
        game.player.hp = 1
    _clear_level_objects_v9(game)
    game.cam_x = 0
    if getattr(game, "state", "") in ("menu", "tutorial", "story_update", "game_over", "victory"):
        game.state = "playing"
        stop_story_music()
    game.add_text(game.player.x + 20, 32, "JUMPED TO LV " + str(level), C14)
globals()["_dev_set_level"] = _dev_set_level_v9
def _dev_rows_v9(game):
    _ensure_creative_state(game)
    _ensure_empire_state(game)
    return [
        ("LEVEL", int(game.level), 1, EMPIRE_TOTAL_LEVELS, 1, lambda v: _dev_set_level_v9(game, v)),
        ("DIST", int(game.distance), 0, LEVEL_DISTANCE - 40, 40, lambda v: setattr(game, "distance", v)),
        ("HP", int(game.player.hp), 1, max(1, int(game.player.max_hp)), 1, lambda v: setattr(game.player, "hp", int(v))),
        ("ROCKETS", int(game.player.rockets), 0, 99, 1, lambda v: setattr(game.player, "rockets", int(v))),
        ("BOMBS", int(game.player.bombs), 0, 99, 1, lambda v: setattr(game.player, "bombs", int(v))),
        ("COINS", int(game.coins_total), 0, 9999, 25, lambda v: setattr(game, "coins_total", int(v))),
        ("SPAWN%", int(game.dev_spawn_pct), 0, 300, 10, lambda v: setattr(game, "dev_spawn_pct", int(v))),
        ("GRAV", int(game.dev_gravity_pct), 20, 120, 5, lambda v: setattr(game, "dev_gravity_pct", int(v))),
        ("MOVE", int(game.dev_move_pct), 8, 32, 1, lambda v: _dev_apply_move(game, v)),
        ("JUMP", int(game.dev_jump_pct), 36, 82, 1, lambda v: _dev_apply_jump(game, v)),
        ("ENEMYMAX", int(game.dev_enemy_cap), 0, 80, 2, lambda v: setattr(game, "dev_enemy_cap", int(v))),
        ("EMPIRE", 1 if getattr(game, "empire_mode", False) else 0, 0, 1, 1, lambda v: _enter_empire_campaign_v9(game, from_dev=True) if int(v) else setattr(game, "empire_mode", False)),
    ]
globals()["_dev_rows"] = _dev_rows_v9
_old_v9_draw_hud = Game.draw_hud
def _draw_hud_v9(self):
    _old_v9_draw_hud(self)
    if self.state not in ("playing", "paused"):
        return
    if self.is_campaign() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"):
        level_max = EMPIRE_TOTAL_LEVELS if getattr(self, "empire_mode", False) else CAMPAIGN_LEVELS
        level_len = 1500 if (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION") else LEVEL_DISTANCE
        bx, by, bw, bh = 76, 151, 174, 32
        fill = int((bw - 16) * clamp(self.distance / max(1, level_len), 0, 1))
        pyxel.rect(bx, by, bw, bh, C0)
        pyxel.rectb(bx, by, bw, bh, C14 if getattr(self, "empire_mode", False) else C7)
        tag = "EMPIRE" if getattr(self, "empire_mode", False) else "CAMPAIGN"
        pyxel.text(bx + 7, by + 5, tag, C10 if getattr(self, "empire_mode", False) else C7)
        pyxel.text(bx + 70, by + 5, "LV " + str(self.level) + "/" + str(level_max), C14)
        pyxel.text(bx + 126, by + 5, str(int(max(0, level_len - self.distance))) + "m", C7)
        pyxel.rect(bx + 8, by + 19, bw - 16, 6, C5)
        pyxel.rect(bx + 8, by + 19, fill, 6, C13 if not getattr(self, "empire_mode", False) else C10)
        pyxel.rectb(bx + 8, by + 19, bw - 16, 6, C7)
    if getattr(self, "empire_mode", False) and getattr(self, "empire_powerup_notice", 0) > 0:
        self.empire_powerup_notice -= 1
        pyxel.rect(52, 42, 216, 28, C0)
        pyxel.rectb(52, 42, 216, 28, C10)
        pyxel.text(66, 49, _better_empire_name_v9(self)[:28], C14)
        pyxel.text(66, 59, "SMALL BOOST: SPEED +0.16  JUMP +0.22  HP=1", C10)
Game.draw_hud = _draw_hud_v9
_old_v9_draw_story_update = Game.draw_story_update
def _draw_story_update_v9(self):
    if getattr(self, "empire_mode", False) and not getattr(self, "empire_won", False):
        pyxel.cls(C0)
        key = self.current_country()["key"]
        name = _better_empire_name_v9(self)
        pyxel.rect(16, 14, 288, 172, C0)
        pyxel.rectb(16, 14, 288, 172, C10)
        pyxel.text(80, 24, "EMPIRE CAMPAIGN CONTINUES", C14)
        pyxel.text(34, 42, self.current_country()["name"] + "  ->  " + name[:24], C10)
        pyxel.text(34, 56, "LEVEL " + str(self.level) + "/" + str(EMPIRE_TOTAL_LEVELS) + "  -  DIFFICULTY KEEPS RISING", C7)
        draw_country_outline(key, 38, 72, scale=3, color=C7, fill=False)
        _draw_empire_flag(key, 130, 82, 36, 22, pyxel.frame_count * .18, True)
        pyxel.rect(202, 68, 56, 68, C1)
        pyxel.rectb(202, 68, 56, 68, C10)
        pyxel.text(205, 73, "UPGRADE", C14)
        _draw_skin_preview(219, 105, self.skin_index, key, empire=True)
        pyxel.text(42, 142, "BUFFS ARE SMALL ON PURPOSE:", C6)
        pyxel.text(42, 153, "SPEED +0.16   JUMP +0.22   ONLY 1 HEALTH BAR", C10)
        pyxel.text(83, 174, "PRESS ENTER TO CONTINUE", C14)
        return
    return _old_v9_draw_story_update(self)
Game.draw_story_update = _draw_story_update_v9
def _draw_empire_ascension_v9(self):
    pyxel.cls(C0)
    _ensure_empire_state(self)
    key = self.current_country()["key"]
    t = getattr(self, "empire_ascension_timer", getattr(self, "empire_anim_timer", 0))
    p = clamp(t / 420.0, 0, 1)
    name = _better_empire_name_v9(self)
    for yy in range(0, SCREEN_H, 4):
        pyxel.line(0, yy, SCREEN_W, yy, C1 if (yy + t) % 18 < 8 else C0)
    pyxel.rect(10, 10, 300, 180, C0)
    pyxel.rectb(10, 10, 300, 180, C10 if (t // 8) % 2 else C14)
    pyxel.text(72, 19, "ASCENSION: DIFFICULTY CONTINUES", C14)
    pyxel.text(72, 31, "CAMPAIGN LEVEL 10  ->  EMPIRE LEVEL 11", C7)
    pyxel.text(26, 48, "TERRITORY", C7)
    pyxel.rect(26, 57, 112, 8, C1); pyxel.rectb(26, 57, 112, 8, C7); pyxel.rect(27, 58, int(110 * p), 6, C3)
    pyxel.text(26, 72, "MILITARY POWER", C7)
    pyxel.rect(26, 81, 112, 8, C1); pyxel.rectb(26, 81, 112, 8, C7); pyxel.rect(27, 82, int(110 * min(1, p * 1.15)), 6, C8)
    pyxel.text(26, 96, "DIFFICULTY", C7)
    pyxel.rect(26, 105, 112, 8, C1); pyxel.rectb(26, 105, 112, 8, C7); pyxel.rect(27, 106, int(110 * min(1, .52 + p * .48)), 6, C14)
    cx, cy = 169, 104
    pyxel.rect(cx - 28, cy - 44, 56, 88, C1)
    pyxel.rectb(cx - 28, cy - 44, 56, 88, C10)
    for i in range(6):
        yy = cy - 38 + i * 14
        pyxel.line(cx - 24, yy, cx + 24, yy + int(math.sin((t+i*9)/10)*4), C5)
    _draw_skin_preview(cx - 8, cy + 8, self.skin_index, key, empire=(t > 130))
    if t > 95:
        _draw_empire_flag(key, 214, 62, 54, 32, pyxel.frame_count * .22, True)
        pyxel.text(211, 100, name[:17], C10)
    if t > 150:
        pyxel.rect(72, 142, 178, 31, C1)
        pyxel.rectb(72, 142, 178, 31, C10)
        pyxel.text(84, 149, "UPGRADE STATS", C14)
        pyxel.text(84, 160, "SPEED +0.16   JUMP +0.22   HP=1", C10)
    if t > 300:
        pyxel.text(92, 180, "ENTER TO DEPLOY INTO LEVEL 11", C14)
Game.draw_empire_ascension = _draw_empire_ascension_v9
_old_v9_update_playing = Game.update_playing
def _update_playing_v9(self):
    _ensure_creative_state(self)
    _ensure_empire_state(self)
    result = _old_v9_update_playing(self)
    if getattr(self, "empire_mode", False):
        _apply_empire_bonuses_v9(self)
    if not hasattr(self, "supply_drops"):
        self.supply_drops = []
    if not hasattr(self, "field_order_cooldown"):
        self.field_order_cooldown = 4 * FPS
    return result
Game.update_playing = _update_playing_v9
FINAL_BUILD_TAG = "FINAL v10"
FINAL_DEBUG_DEFAULT = False
def _difficulty_value_v10(self):
    campaign_like = self.is_campaign() or (
        self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"
    )
    if campaign_like:
        lvl = int(getattr(self, "level", 1))
        pr = self.progress_ratio()
        if lvl <= CAMPAIGN_LEVELS:
            return 0.85 + lvl * 0.66 + pr * 1.15
        return 7.45 + (lvl - CAMPAIGN_LEVELS) * 0.54 + pr * 1.25
    return 0.95 + self.survival_frames / 940.0
Game.difficulty_value = _difficulty_value_v10
def _danger_scale_v10(self):
    campaign_like = self.is_campaign() or (
        self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"
    )
    if campaign_like:
        lvl = int(getattr(self, "level", 1))
        pr = self.progress_ratio()
        if lvl <= CAMPAIGN_LEVELS:
            return 0.82 + (lvl - 1) * 0.145 + pr * 0.78
        return 2.45 + (lvl - CAMPAIGN_LEVELS) * 0.135 + pr * 0.88
    return 0.95 + self.survival_frames / 840.0
Game.danger_scale = _danger_scale_v10
def _missile_spawn_chance_v10(self):
    d = self.danger_scale()
    return min(0.0025 + d * 0.0031, 0.043)
Game.missile_spawn_chance = _missile_spawn_chance_v10
def _drone_spawn_chance_v10(self):
    d = self.danger_scale()
    return min(0.0009 + d * 0.00145, 0.017)
Game.drone_spawn_chance = _drone_spawn_chance_v10
def _turret_spawn_chance_v10(self):
    d = self.difficulty_value()
    if d < 2.8:
        return 0
    return min(0.00045 + d * 0.00072, 0.0065)
Game.turret_spawn_chance = _turret_spawn_chance_v10
def _bomber_spawn_chance_v10(self):
    d = self.difficulty_value()
    if d < 4.4:
        return 0
    return min(0.00015 + d * 0.00010, 0.00125)
Game.bomber_spawn_chance = _bomber_spawn_chance_v10
EMPIRE_SPEED_BONUS_FINAL = 0.10
EMPIRE_AIR_BONUS_FINAL = 0.08
EMPIRE_JUMP_BONUS_FINAL = 0.14
def _apply_empire_bonuses_v10(game):
    if not getattr(game, "empire_mode", False):
        return
    _ensure_v9_state(game)
    base_speed = getattr(game, "empire_base_speed", 1.8)
    base_air = getattr(game, "empire_base_air_speed", 1.5)
    base_jump = getattr(game, "empire_base_jump_power", -5.5)
    game.player.speed = base_speed + EMPIRE_SPEED_BONUS_FINAL
    game.player.air_speed = base_air + EMPIRE_AIR_BONUS_FINAL
    game.player.jump_power = base_jump - EMPIRE_JUMP_BONUS_FINAL
    game.player.max_hp = 1
    game.player.hp = min(max(1, game.player.hp), 1)
globals()["_apply_empire_bonuses"] = _apply_empire_bonuses_v10
def _final_feature_watchdog(game):
    for name in ["supply_drops", "effects", "particles", "texts", "powerups", "coins", "player_bullets", "enemy_bullets", "rockets", "bombs", "enemies"]:
        if not hasattr(game, name) or getattr(game, name) is None:
            setattr(game, name, [])
    if not hasattr(game, "field_order"):
        game.field_order = None
    if not hasattr(game, "field_order_progress"):
        game.field_order_progress = 0
    if not hasattr(game, "field_order_timer"):
        game.field_order_timer = 0
    if not hasattr(game, "field_order_cooldown"):
        game.field_order_cooldown = 4 * FPS
    if not hasattr(game, "field_order_kills"):
        game.field_order_kills = 0
    if not hasattr(game, "field_order_near"):
        game.field_order_near = 0
    if not hasattr(game, "damage_taken_run"):
        game.damage_taken_run = 0
    if not hasattr(game, "dev_mode"):
        game.dev_mode = False
    if not hasattr(game, "dev_panel_open"):
        game.dev_panel_open = False
    if not hasattr(game, "dev_buffer"):
        game.dev_buffer = ""
    if not hasattr(game, "debug_overlay"):
        game.debug_overlay = FINAL_DEBUG_DEFAULT
    if not hasattr(game, "mp_scores") or not isinstance(game.mp_scores, list):
        game.mp_scores = [0 for _ in range(max(2, getattr(game, "mp_player_count", 2)))]
    while len(game.mp_scores) < max(2, getattr(game, "mp_player_count", 2)):
        game.mp_scores.append(0)
    if not hasattr(game, "mp_current_player"):
        game.mp_current_player = 0
    if not hasattr(game, "mp_campaign_segments_left"):
        game.mp_campaign_segments_left = CAMPAIGN_LEVELS
    for flag in ["final_bunker_active", "final_bunker_ready", "final_bunker_spawned", "awaiting_final_bunker", "boss_final_bunker_mode"]:
        if hasattr(game, flag):
            setattr(game, flag, False)
    if hasattr(game, "final_bunker_x"):
        game.final_bunker_x = -999999
    if not hasattr(game, "boss_killed_this_level"):
        game.boss_killed_this_level = False
    if not hasattr(game, "level_boss_spawned"):
        game.level_boss_spawned = False
    if getattr(game, "empire_mode", False):
        if getattr(game, "level", 1) <= CAMPAIGN_LEVELS:
            game.level = CAMPAIGN_LEVELS + 1
        _apply_empire_bonuses_v10(game)
REAL_EMPIRE_NAMES["usa"] = "AMERICAN CONTINENTAL EMPIRE"
EMPIRE_NAMES["usa"] = "AMERICAN CONTINENTAL EMPIRE"
if "usa" in MAJOR_EMPIRE_DATA:
    MAJOR_EMPIRE_DATA["usa"].update({
        "empire": "AMERICAN CONTINENTAL EMPIRE",
        "power": "FEDERAL STRIKE COMMANDS",
        "growth": "COAST-TO-COAST UNION",
        "territory": "CONTINENTAL DEFENSE ZONE",
        "icon": "EAGLE UNION STANDARD",
    })
def _better_empire_name_v10(game):
    return REAL_EMPIRE_NAMES.get(game.current_country()["key"], game.current_country()["name"] + " EMPIRE")
globals()["_empire_name"] = _better_empire_name_v10
def _enter_empire_campaign_v10(game, from_dev=False):
    _ensure_empire_state(game)
    _final_feature_watchdog(game)
    game.empire_mode = True
    game.empire_won = False
    game.level = CAMPAIGN_LEVELS + 1
    game.distance = 0
    game.checkpoint_level = game.level
    game.checkpoint_x = 40
    game.terrain.reset()
    game.player.reset(40, game.difficulty_value())
    _apply_empire_bonuses_v10(game)
    game.player.hp = 1
    game.player.invuln = max(game.player.invuln, FPS)
    game.level_boss_spawned = False
    game.boss_killed_this_level = False
    game.empire_powerup_notice = FPS * 5
    for name in ["player_bullets", "enemy_bullets", "rockets", "bombs", "enemies", "effects", "particles", "texts", "powerups", "coins"]:
        try:
            getattr(game, name).clear()
        except Exception:
            pass
    game.state = "playing" if from_dev else "story_update"
    game.intro_timer = 0
    stop_story_music()
globals()["_enter_empire_campaign"] = _enter_empire_campaign_v10
def _next_level_v10(self):
    _final_feature_watchdog(self)
    if not getattr(self, "empire_mode", False):
        if self.level >= CAMPAIGN_LEVELS:
            try:
                _start_empire_ascension(self)
            except Exception:
                _enter_empire_campaign_v10(self, from_dev=False)
            self.story_progress_index += 1
            return
        self.level += 1
        self.distance = 0
        self.checkpoint_level = self.level
        self.checkpoint_x = 40
        self.terrain.reset()
        self.level_boss_spawned = False
        self.boss_killed_this_level = False
        for name in ["player_bullets", "enemy_bullets", "rockets", "bombs", "enemies", "effects", "particles", "texts", "powerups", "coins"]:
            try:
                getattr(self, name).clear()
            except Exception:
                pass
        self.player.reset(self.checkpoint_x, self.difficulty_value())
        self.player.max_hp_bonus = max(self.player.max_hp_bonus, self.extra_hp_bonus)
        self.player.max_hp = 5 + self.player.max_hp_bonus
        self.player.hp = self.player.max_hp
        self.story_progress_index += 1
        self.state = "story_update"
        self.intro_timer = 0
        play_story_music()
        return
    self.level += 1
    self.distance = 0
    self.checkpoint_level = self.level
    self.checkpoint_x = 40
    self.terrain.reset()
    self.level_boss_spawned = False
    self.boss_killed_this_level = False
    for name in ["player_bullets", "enemy_bullets", "rockets", "bombs", "enemies", "effects", "particles", "texts", "powerups", "coins"]:
        try:
            getattr(self, name).clear()
        except Exception:
            pass
    if self.level > EMPIRE_TOTAL_LEVELS:
        self.empire_won = True
        self.state = "victory"
        play_story_music()
    else:
        self.player.reset(self.checkpoint_x, self.difficulty_value())
        _apply_empire_bonuses_v10(self)
        self.player.hp = 1
        self.story_progress_index += 1
        self.state = "story_update"
        self.intro_timer = 0
        play_story_music()
Game.next_level = _next_level_v10
FINAL_BOSS_LEVELS_NORMAL = {3, 6, 10}
FINAL_BOSS_LEVELS_EMPIRE = {12, 15, 18, 20}
def _is_final_boss_level(game):
    lvl = int(getattr(game, "level", 1))
    return lvl in (FINAL_BOSS_LEVELS_EMPIRE if getattr(game, "empire_mode", False) else FINAL_BOSS_LEVELS_NORMAL)
_old_v10_maybe_spawn_enemies_base = Game.maybe_spawn_enemies
def _maybe_spawn_enemies_v10(self):
    _final_feature_watchdog(self)
    if getattr(self, "boss_death_cinematic", 0) > 0 or getattr(self, "boss_killed_this_level", False):
        return
    boss_alive = any(isinstance(e, BossEnemy) and getattr(e, "alive", True) for e in self.enemies)
    campaign_like = self.is_campaign() or (
        self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"
    )
    if campaign_like and _is_final_boss_level(self) and self.progress_ratio() > 0.82:
        if (not boss_alive) and (not getattr(self, "level_boss_spawned", False)):
            self.level_boss_spawned = True
            self.enemies.append(BossEnemy(self.player.x + 230))
            self.add_text(self.player.x + 78, 34, "BOSS INCOMING", C8)
            sfx(0, 7)
        return
    return _old_v10_maybe_spawn_enemies_base(self)
Game.maybe_spawn_enemies = _maybe_spawn_enemies_v10
_old_v10_kill_enemy_base = Game.kill_enemy
def _kill_enemy_v10(self, enemy):
    was_boss = isinstance(enemy, BossEnemy)
    _old_v10_kill_enemy_base(self, enemy)
    if was_boss:
        self.boss_killed_this_level = True
        self.level_boss_spawned = True
        self.boss_death_cinematic = max(getattr(self, "boss_death_cinematic", 0), FPS * 2)
        for flag in ["final_bunker_active", "final_bunker_ready", "final_bunker_spawned", "awaiting_final_bunker", "boss_final_bunker_mode"]:
            if hasattr(self, flag):
                setattr(self, flag, False)
        self.enemies = [e for e in self.enemies if not isinstance(e, BossEnemy)]
        self.add_text(self.player.x + 60, 40, "BOSS DESTROYED", C14)
Game.kill_enemy = _kill_enemy_v10
_old_v10_update_base = Game.update
def _update_v10(self):
    _final_feature_watchdog(self)
    f3 = getattr(pyxel, "KEY_F3", None)
    if f3 is not None and pyxel.btnp(f3):
        self.debug_overlay = not getattr(self, "debug_overlay", False)
    for key, ch in [(getattr(pyxel, "KEY_D", None), "D"), (getattr(pyxel, "KEY_B", None), "B"), (getattr(pyxel, "KEY_G", None), "G")]:
        if key is not None and pyxel.btnp(key):
            self._debug_buffer = (getattr(self, "_debug_buffer", "") + ch)[-3:]
            if self._debug_buffer == "DBG":
                self.debug_overlay = not getattr(self, "debug_overlay", False)
                self._debug_buffer = ""
    result = _old_v10_update_base(self)
    _final_feature_watchdog(self)
    return result
Game.update = _update_v10
_old_v10_update_playing_base = Game.update_playing
def _update_playing_v10(self):
    _final_feature_watchdog(self)
    if getattr(self, "boss_death_cinematic", 0) > 0:
        self.boss_death_cinematic -= 1
        self.update_shake()
        for p in self.particles:
            p.update()
        for e in self.effects:
            e.update()
        self.particles = [p for p in self.particles if p.alive]
        self.effects = [e for e in self.effects if e.alive]
        if self.boss_death_cinematic <= 0:
            self.next_level()
        return
    result = _old_v10_update_playing_base(self)
    _final_feature_watchdog(self)
    return result
Game.update_playing = _update_playing_v10
def _draw_debug_overlay_v10(game):
    if not (getattr(game, "debug_overlay", False) or (getattr(game, "dev_mode", False) and getattr(game, "dev_panel_open", False))):
        return
    x, y, w, h = 5, 38, 146, 92
    _final_feature_watchdog(game)
    boss_count = sum(1 for e in game.enemies if isinstance(e, BossEnemy))
    live_enemies = sum(1 for e in game.enemies if getattr(e, "alive", True))
    pyxel.rect(x, y, w, h, C0)
    pyxel.rectb(x, y, w, h, C11 if getattr(game, "debug_overlay", False) else C5)
    pyxel.text(x + 4, y + 4, "DEBUG " + FINAL_BUILD_TAG, C14)
    pyxel.text(x + 4, y + 15, "STATE " + str(getattr(game, "state", "?"))[:14], C7)
    pyxel.text(x + 4, y + 25, "LV " + str(game.level) + "  DIST " + str(int(game.distance)), C7)
    pyxel.text(x + 4, y + 35, "DIFF %.2f  DANGER %.2f" % (game.difficulty_value(), game.danger_scale()), C10)
    pyxel.text(x + 4, y + 45, "EN " + str(live_enemies) + "  BOSS " + str(boss_count) + "  GATE " + str(int(getattr(game, "level_boss_spawned", False))), C8 if boss_count else C7)
    pyxel.text(x + 4, y + 55, "SUPPLY " + str(len(game.supply_drops)) + "  ORDER " + ("ON" if game.field_order else "WAIT"), C3 if hasattr(game, "supply_drops") else C8)
    pyxel.text(x + 4, y + 65, "MODE " + ("EMP" if getattr(game, "empire_mode", False) else "NORM") + "  HP " + str(game.player.hp) + "/" + str(game.player.max_hp), C14)
    pyxel.text(x + 4, y + 75, "MP P" + str(getattr(game, "mp_current_player", 0) + 1) + " SCORES " + "/".join(str(int(s)) for s in game.mp_scores[:4]), C6)
    pyxel.text(x + 4, y + 85, "DBG/F3 TOGGLE  OOO DEV", C11)
globals()["_draw_debug_overlay"] = _draw_debug_overlay_v10
_old_v10_draw_hud_base = Game.draw_hud
def _draw_hud_v10(self):
    _old_v10_draw_hud_base(self)
    if getattr(self, "empire_mode", False) and getattr(self, "empire_powerup_notice", 0) > 0:
        pyxel.rect(52, 42, 216, 28, C0)
        pyxel.rectb(52, 42, 216, 28, C10)
        pyxel.text(66, 49, _better_empire_name_v10(self)[:28], C14)
        pyxel.text(66, 59, "FINAL BOOST: SPEED +0.10  JUMP +0.14  HP=1", C10)
    _draw_debug_overlay_v10(self)
Game.draw_hud = _draw_hud_v10
_old_v10_draw_story_update_base = Game.draw_story_update
def _draw_story_update_v10(self):
    if getattr(self, "empire_mode", False) and not getattr(self, "empire_won", False):
        pyxel.cls(C0)
        key = self.current_country()["key"]
        name = _better_empire_name_v10(self)
        pyxel.rect(16, 14, 288, 172, C0)
        pyxel.rectb(16, 14, 288, 172, C10)
        pyxel.text(74, 24, "EMPIRE CAMPAIGN - FINAL BUILD", C14)
        pyxel.text(34, 42, self.current_country()["name"] + "  ->  " + name[:25], C10)
        pyxel.text(34, 55, "LEVEL " + str(self.level) + "/20  DIFFICULTY CONTINUES", C7)
        if key == "usa":
            pyxel.text(34, 68, "UPGRADE MEANS: CONTINENTAL UNION COMMAND", C6)
        else:
            pyxel.text(34, 68, "UPGRADE MEANS: HISTORIC IMPERIAL WAR FORM", C6)
        draw_country_outline(key, 38, 82, scale=3, color=C7, fill=False)
        try:
            _draw_empire_flag(key, 130, 87, 38, 23, pyxel.frame_count * .18, True)
        except Exception:
            draw_flag(key, 130, 87, 38, 23, pyxel.frame_count * .18)
        pyxel.rect(202, 70, 56, 68, C1)
        pyxel.rectb(202, 70, 56, 68, C10)
        pyxel.text(205, 75, "AVATAR", C14)
        _draw_skin_preview(219, 107, self.skin_index, key, empire=True)
        pyxel.text(42, 145, "BALANCED BOOSTS:", C6)
        pyxel.text(42, 156, "SPEED +0.10   JUMP +0.14   ONLY 1 HEALTH BAR", C10)
        pyxel.text(80, 176, "ENTER TO CONTINUE", C14)
        return
    return _old_v10_draw_story_update_base(self)
Game.draw_story_update = _draw_story_update_v10
def _draw_empire_ascension_v10(self):
    pyxel.cls(C0)
    _ensure_empire_state(self)
    key = self.current_country()["key"]
    t = getattr(self, "empire_ascension_timer", getattr(self, "empire_anim_timer", 0))
    p = clamp(t / 520.0, 0, 1)
    name = _better_empire_name_v10(self)
    for yy in range(0, SCREEN_H, 5):
        col = C1 if (yy * 2 + t) % 34 < 15 else C0
        pyxel.line(0, yy, SCREEN_W, yy, col)
    pyxel.rect(8, 8, 304, 184, C0)
    pyxel.rectb(8, 8, 304, 184, C10 if (t // 10) % 2 else C14)
    pyxel.text(67, 18, "ASCENSION BUFFER - FINAL TUNING", C14)
    pyxel.text(64, 30, "LEVEL 10 COMPLETE  ->  LEVEL 11 CONTINUES", C7)
    labels = [("TERRITORY", C3, p), ("MILITARY", C8, min(1, p*1.10)), ("DIFFICULTY", C14, min(1, .52 + p*.48))]
    for i, (lab, col, val) in enumerate(labels):
        yy = 50 + i * 22
        pyxel.text(24, yy, lab, C7)
        pyxel.rect(88, yy, 104, 7, C1)
        pyxel.rectb(88, yy, 104, 7, C7)
        pyxel.rect(89, yy+1, int(102*val), 5, col)
    cx, cy = 163, 126
    pyxel.rect(cx - 31, cy - 48, 62, 76, C1)
    pyxel.rectb(cx - 31, cy - 48, 62, 76, C10)
    for i in range(7):
        pyxel.line(cx - 28, cy - 42 + i*12, cx + 28, cy - 42 + i*12 + int(math.sin((t+i*11)/11)*5), C5)
    _draw_skin_preview(cx - 8, cy - 4, self.skin_index, key, empire=t > 160)
    if t > 110:
        try:
            _draw_empire_flag(key, 220, 61, 54, 32, pyxel.frame_count * .22, True)
        except Exception:
            draw_flag(key, 220, 61, 54, 32, pyxel.frame_count * .22)
        pyxel.text(208, 98, name[:19], C10)
    if t > 210:
        pyxel.rect(56, 152, 210, 28, C1)
        pyxel.rectb(56, 152, 210, 28, C10)
        pyxel.text(66, 158, "STATS: SPEED +0.10   JUMP +0.14   HP=1", C10)
        pyxel.text(66, 168, "BALANCED SO LEVELS 11-20 ARE BEATABLE", C7)
    if t > 395:
        pyxel.text(84, 184, "PRESS ENTER TO DEPLOY", C14)
Game.draw_empire_ascension = _draw_empire_ascension_v10
def _dev_rows_v10(game):
    _ensure_creative_state(game)
    _ensure_empire_state(game)
    _final_feature_watchdog(game)
    return [
        ("LEVEL", int(game.level), 1, EMPIRE_TOTAL_LEVELS, 1, lambda v: _dev_set_level_v9(game, v)),
        ("DIST", int(game.distance), 0, LEVEL_DISTANCE - 40, 40, lambda v: setattr(game, "distance", v)),
        ("HP", int(game.player.hp), 1, max(1, int(game.player.max_hp)), 1, lambda v: setattr(game.player, "hp", int(v))),
        ("ROCKETS", int(game.player.rockets), 0, 99, 1, lambda v: setattr(game.player, "rockets", int(v))),
        ("BOMBS", int(game.player.bombs), 0, 99, 1, lambda v: setattr(game.player, "bombs", int(v))),
        ("COINS", int(game.coins_total), 0, 9999, 25, lambda v: setattr(game, "coins_total", int(v))),
        ("SPAWN%", int(game.dev_spawn_pct), 0, 220, 10, lambda v: setattr(game, "dev_spawn_pct", int(v))),
        ("GRAV", int(game.dev_gravity_pct), 20, 120, 5, lambda v: setattr(game, "dev_gravity_pct", int(v))),
        ("MOVE", int(game.dev_move_pct), 8, 32, 1, lambda v: _dev_apply_move(game, v)),
        ("JUMP", int(game.dev_jump_pct), 36, 82, 1, lambda v: _dev_apply_jump(game, v)),
        ("ENEMYMAX", int(game.dev_enemy_cap), 0, 60, 2, lambda v: setattr(game, "dev_enemy_cap", int(v))),
        ("DEBUG", 1 if getattr(game, "debug_overlay", False) else 0, 0, 1, 1, lambda v: setattr(game, "debug_overlay", bool(int(v)))),
        ("EMPIRE", 1 if getattr(game, "empire_mode", False) else 0, 0, 1, 1, lambda v: _enter_empire_campaign_v10(game, from_dev=True) if int(v) else setattr(game, "empire_mode", False)),
    ]
globals()["_dev_rows"] = _dev_rows_v10
FINAL_FEATURE_CHECKS = {
    "SupplyDrop": "SupplyDrop" in globals(),
    "FieldOrders": "_update_field_order" in globals() and "_complete_field_order" in globals(),
    "DevPanel": "_draw_dev_panel" in globals(),
    "Empire": "EMPIRE_TOTAL_LEVELS" in globals(),
    "Boss": "BossEnemy" in globals(),
    "Multiplayer": hasattr(Game, "reset_multiplayer_session"),
}
def _draw_debug_overlay_v11(game):
    if not getattr(game, "debug_overlay", False):
        return
    x, y, w, h = 5, 38, 136, 70
    try:
        _final_feature_watchdog(game)
    except Exception:
        pass
    boss_count = sum(1 for e in getattr(game, "enemies", []) if isinstance(e, BossEnemy))
    live_enemies = sum(1 for e in getattr(game, "enemies", []) if getattr(e, "alive", True))
    pyxel.rect(x, y, w, h, C0)
    pyxel.rectb(x, y, w, h, C11)
    pyxel.text(x + 4, y + 4, "DEBUG", C14)
    pyxel.text(x + 4, y + 15, "STATE " + str(getattr(game, "state", "?"))[:13], C7)
    pyxel.text(x + 4, y + 25, "LV " + str(getattr(game, "level", 1)) + "  DIST " + str(int(getattr(game, "distance", 0))), C7)
    pyxel.text(x + 4, y + 35, "DIFF %.2f" % game.difficulty_value(), C10)
    pyxel.text(x + 4, y + 45, "EN " + str(live_enemies) + "  BOSS " + str(boss_count), C8 if boss_count else C7)
    pyxel.text(x + 4, y + 55, "DBG/F3 HIDE", C11)
globals()["_draw_debug_overlay"] = _draw_debug_overlay_v11
def _draw_hud_v11(self):
    try:
        _old_v9_draw_hud(self)
    except Exception:
        try:
            _old_v8_draw_hud(self)
        except Exception:
            pass
    if getattr(self, "state", "") in ("playing", "paused"):
        if self.is_campaign() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"):
            level_max = EMPIRE_TOTAL_LEVELS if getattr(self, "empire_mode", False) else CAMPAIGN_LEVELS
            level_len = 1500 if (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION") else LEVEL_DISTANCE
            remaining = max(0, int(level_len - getattr(self, "distance", 0)))
            tag = "EMP" if getattr(self, "empire_mode", False) else "LV"
            pyxel.text(6, 185, tag + " " + str(getattr(self, "level", 1)) + "/" + str(level_max) + "   " + str(remaining) + "m", C10 if getattr(self, "empire_mode", False) else C7)
        if getattr(self, "empire_mode", False) and getattr(self, "empire_powerup_notice", 0) > 0:
            self.empire_powerup_notice -= 1
            pyxel.text(92, 185, "SPEED +0.10  JUMP +0.14  HP=1", C10)
    _draw_debug_overlay_v11(self)
Game.draw_hud = _draw_hud_v11
def _dev_rows_v11(game):
    _ensure_creative_state(game)
    _ensure_empire_state(game)
    try:
        _final_feature_watchdog(game)
    except Exception:
        pass
    return [
        ("LEVEL", int(game.level), 1, EMPIRE_TOTAL_LEVELS, 1, lambda v: _dev_set_level_v9(game, v)),
        ("DIST", int(game.distance), 0, LEVEL_DISTANCE - 40, 40, lambda v: setattr(game, "distance", v)),
        ("HP", int(game.player.hp), 1, max(1, int(game.player.max_hp)), 1, lambda v: setattr(game.player, "hp", int(v))),
        ("ROCKETS", int(game.player.rockets), 0, 99, 1, lambda v: setattr(game.player, "rockets", int(v))),
        ("BOMBS", int(game.player.bombs), 0, 99, 1, lambda v: setattr(game.player, "bombs", int(v))),
        ("COINS", int(game.coins_total), 0, 9999, 25, lambda v: setattr(game, "coins_total", int(v))),
        ("SPAWN%", int(game.dev_spawn_pct), 0, 220, 10, lambda v: setattr(game, "dev_spawn_pct", int(v))),
        ("GRAV", int(game.dev_gravity_pct), 20, 120, 5, lambda v: setattr(game, "dev_gravity_pct", int(v))),
        ("MOVE", int(game.dev_move_pct), 8, 32, 1, lambda v: _dev_apply_move(game, v)),
        ("JUMP", int(game.dev_jump_pct), 36, 82, 1, lambda v: _dev_apply_jump(game, v)),
        ("ENEMYMAX", int(game.dev_enemy_cap), 0, 60, 2, lambda v: setattr(game, "dev_enemy_cap", int(v))),
        ("EMPIRE", 1 if getattr(game, "empire_mode", False) else 0, 0, 1, 1, lambda v: _enter_empire_campaign_v10(game, from_dev=True) if int(v) else setattr(game, "empire_mode", False)),
    ]
globals()["_dev_rows"] = _dev_rows_v11
FINAL_DEBUG_DEFAULT = False
_old_v11_reset_run = Game.reset_run
def _reset_run_v11(self):
    _old_v11_reset_run(self)
    if not hasattr(self, "debug_overlay"):
        self.debug_overlay = False
Game.reset_run = _reset_run_v11
_old_v12_draw_hud_previous = Game.draw_hud
def _v12_safe_field_order_state(game):
    if not hasattr(game, "field_order"):
        game.field_order = None
    if not hasattr(game, "field_order_progress"):
        game.field_order_progress = 0
    if not hasattr(game, "field_order_timer"):
        game.field_order_timer = 0
    if not hasattr(game, "field_order_cooldown"):
        game.field_order_cooldown = 4 * FPS
def _draw_v12_radio_mission(game):
    _v12_safe_field_order_state(game)
    if not game.field_order:
        if getattr(game, "field_order_cooldown", 9999) < 3 * FPS:
            x, y, w, h = 82, 28, 156, 20
            pyxel.rect(x, y, w, h, C1)
            pyxel.rectb(x, y, w, h, C12)
            pyxel.text(x + 6, y + 4, "BLUE RADIO MSG", C12)
            pyxel.text(x + 6, y + 12, "NEW FIELD ORDER INCOMING", C7)
        return
    o = game.field_order
    color = C12
    x, y, w, h = 66, 28, 188, 38
    pyxel.rect(x, y, w, h, C0)
    pyxel.rectb(x, y, w, h, color)
    pyxel.rect(x + 2, y + 2, w - 4, 9, C1)
    pyxel.text(x + 7, y + 4, "BLUE RADIO MSG  //  FIELD ORDER", C12)
    code = str(o.get("code", "ORDER"))
    brief = str(o.get("brief", "Complete the order"))
    pyxel.text(x + 7, y + 15, (code + ": " + brief)[:37], C7)
    goal = max(1, int(o.get("goal", 1)))
    prog = max(0, int(getattr(game, "field_order_progress", 0)))
    fill = int(126 * clamp(prog / goal, 0, 1))
    pyxel.rect(x + 7, y + 28, 126, 4, C5)
    pyxel.rect(x + 7, y + 28, fill, 4, color)
    pyxel.rectb(x + 7, y + 28, 126, 4, C7)
    pyxel.text(x + 139, y + 25, str(prog) + "/" + str(goal), C7)
    pyxel.text(x + 164, y + 4, str(max(0, int(getattr(game, "field_order_timer", 0)) // FPS)) + "s", C6)
def _draw_v12_tiny_progress_text(game):
    if not (game.is_campaign() or (game.is_multiplayer() and game.mp_mode_names[game.mp_mode_index] == "CAMPAIGN ROTATION")):
        if game.is_survival() or (game.is_multiplayer() and game.mp_mode_names[game.mp_mode_index] == "SURVIVAL ROTATION"):
            pyxel.text(6, 184, "TIME " + str(int(getattr(game, "survival_frames", 0)) // 60), C7)
        return
    level_len = 1500 if (game.is_multiplayer() and game.mp_mode_names[game.mp_mode_index] == "CAMPAIGN ROTATION") else LEVEL_DISTANCE
    remaining = max(0, int(level_len - getattr(game, "distance", 0)))
    if getattr(game, "empire_mode", False):
        text = "EMP " + str(getattr(game, "level", 1)) + "/" + str(EMPIRE_TOTAL_LEVELS) + "  " + str(remaining) + "m"
        pyxel.text(6, 184, text, C10)
    else:
        text = "LV " + str(getattr(game, "level", 1)) + "/" + str(CAMPAIGN_LEVELS) + "  " + str(remaining) + "m"
        pyxel.text(6, 184, text, C7)
def _draw_v12_multiplayer_text(game):
    if not (game.is_multiplayer() and getattr(game, "state", "") in ("playing", "paused")):
        return
    parts = []
    for i in range(min(getattr(game, "mp_player_count", 2), len(getattr(game, "mp_scores", [])))):
        prefix = "*" if i == getattr(game, "mp_current_player", 0) else ""
        parts.append(prefix + "P" + str(i + 1) + ":" + str(game.mp_scores[i]))
    pyxel.text(180, 184, "  ".join(parts)[:33], C14)
def _draw_hud_v12(self):
    try:
        pyxel.rect(0, 0, SCREEN_W, 24, C0)
        pyxel.rectb(0, 0, SCREEN_W, 24, C5)
        pyxel.text(6, 5, "HP", C7)
        max_hp = max(1, min(12, int(getattr(self.player, "max_hp", 1))))
        for i in range(max_hp):
            col = C8 if i < getattr(self.player, "hp", 0) else C5
            pyxel.rect(22 + i * 9, 5, 7, 6, col)
        pyxel.text(6, 15, "$" + str(getattr(self, "coins_total", 0)), C10)
        pyxel.text(68, 5, "WPN " + str(getattr(self.player, "weapon", "BLASTER"))[:7], C7)
        pyxel.text(150, 5, "R" + str(getattr(self.player, "rockets", 0)), C10)
        pyxel.text(184, 5, "B" + str(getattr(self.player, "bombs", 0)), C8)
        pyxel.text(220, 5, "SCORE " + str(getattr(self.player, "score", 0)), C14)
        if getattr(self, "dev_mode", False):
            pyxel.text(270, 15, "DEV", C14)
        try:
            draw_flag(self.current_country()["key"], 296, 13, 14, 8, pyxel.frame_count * 0.2)
        except Exception:
            pass
        y = 28
        buffs = []
        if getattr(self.player, "rapid_timer", 0) > 0: buffs.append("RAPID " + str(self.player.rapid_timer // 60) + "s")
        if getattr(self.player, "spread_timer", 0) > 0: buffs.append("SPREAD " + str(self.player.spread_timer // 60) + "s")
        if getattr(self.player, "overdrive_timer", 0) > 0: buffs.append("OVER " + str(self.player.overdrive_timer // 60) + "s")
        if getattr(self.player, "invuln", 0) > 90: buffs.append("SHIELD " + str(self.player.invuln // 60) + "s")
        for b in buffs[:4]:
            pyxel.text(6, y, b, C10)
            y += 8
        if getattr(self.player, "combo", 0) > 1:
            pyxel.text(6, 166, "COMBO x" + str(self.player.combo), C10)
        _draw_v12_radio_mission(self)
        _draw_v12_tiny_progress_text(self)
        _draw_v12_multiplayer_text(self)
        if getattr(self, "empire_mode", False) and getattr(self, "empire_powerup_notice", 0) > 0:
            self.empire_powerup_notice -= 1
            pyxel.text(78, 184, "SPEED +0.10  JUMP +0.14  HP=1", C10)
        if getattr(self, "dev_mode", False) and getattr(self, "dev_panel_open", False):
            try:
                _draw_dev_panel(self)
            except Exception:
                pass
        try:
            _draw_debug_overlay_v11(self)
        except Exception:
            try:
                _draw_debug_overlay(self)
            except Exception:
                pass
    except Exception:
        pyxel.text(6, 5, "HUD SAFE MODE", C8)
        _draw_v12_tiny_progress_text(self)
Game.draw_hud = _draw_hud_v12
_old_v12_complete_field_order = globals().get("_complete_field_order")
def _complete_field_order_v12(game):
    if _old_v12_complete_field_order:
        _old_v12_complete_field_order(game)
    try:
        game.order_complete_timer = max(getattr(game, "order_complete_timer", 0), 90)
        game.order_complete_title = "RADIO ORDER COMPLETE"
        game.order_complete_reward = "+SUPPLY   +REWARD   GOOD WORK"
        game.add_text(game.player.x + 18, 34, "BLUE RADIO: MISSION COMPLETE", C12)
    except Exception:
        pass
globals()["_complete_field_order"] = _complete_field_order_v12
_EXTRA_SKINS_V13 = [
    {"name": "DESERT RAIDER", "body": C10, "cape": C4, "visor": C7, "form": "hood"},
    {"name": "ARCTIC VANGUARD", "body": C7, "cape": C12, "visor": C6, "form": "fur"},
    {"name": "DRONE CAPTAIN", "body": C13, "cape": C5, "visor": C11, "form": "antenna"},
    {"name": "ROYAL GUARD", "body": C8, "cape": C14, "visor": C10, "form": "plume"},
    {"name": "SHADOW PILOT", "body": C0, "cape": C5, "visor": C12, "form": "wings"},
    {"name": "GOLD SENTINEL", "body": C10, "cape": C14, "visor": C7, "form": "halo"},
]
try:
    _existing_skin_names_v13 = {s.get("name") for s in Player.SKINS}
    for _s in _EXTRA_SKINS_V13:
        if _s["name"] not in _existing_skin_names_v13:
            Player.SKINS.append(_s)
except Exception:
    pass
try:
    while len(SKIN_PRICES) < len(Player.SKINS):
        SKIN_PRICES.append(90 + len(SKIN_PRICES) * 18)
except Exception:
    pass
EMPIRE_FORM_V13 = {
    "usa":     {"title":"CONTINENTAL EAGLE COMMANDER", "body":C12, "cape":C1,  "trim":C7,  "shape":"eagle"},
    "china":   {"title":"QING DRAGON GUARD",            "body":C10, "cape":C14, "trim":C8,  "shape":"dragon"},
    "russia":  {"title":"IMPERIAL WINTER TSAR",         "body":C7,  "cape":C5,  "trim":C10, "shape":"fur"},
    "germany": {"title":"KAISER IRON MARSHAL",          "body":C5,  "cape":C0,  "trim":C7,  "shape":"spike"},
    "uk":      {"title":"BRITANNIC CROWN ADMIRAL",      "body":C1,  "cape":C12, "trim":C7,  "shape":"crown"},
    "france":  {"title":"IMPERIAL EAGLE GENERAL",       "body":C12, "cape":C1,  "trim":C10, "shape":"eagle"},
    "japan":   {"title":"RISING SUN SHOGUN",            "body":C8,  "cape":C10, "trim":C7,  "shape":"sun"},
    "italy":   {"title":"ROMAN LEGION IMPERATOR",       "body":C8,  "cape":C10, "trim":C14, "shape":"roman"},
    "turkiye": {"title":"OTTOMAN CRESCENT SULTAN",      "body":C2,  "cape":C8,  "trim":C10, "shape":"turban"},
    "brazil":  {"title":"IMPERIAL AMAZON GUARD",        "body":C3,  "cape":C10, "trim":C12, "shape":"feather"},
}
def _v13_country_key(game_or_player):
    try:
        if hasattr(game_or_player, 'current_country'):
            return game_or_player.current_country().get('key','usa')
        return game_or_player.country.get('key','usa')
    except Exception:
        return 'usa'
def _v13_draw_banner(key, x, y, w=22, h=13, wave=0):
    try:
        _draw_empire_flag(key, x, y, w, h, wave, empire=True)
    except Exception:
        draw_flag(key, x, y, w, h, wave)
_old_player_draw_v13_base = Player.draw
def _draw_player_form_v13(self, cam_x):
    sx = int(self.x - cam_x)
    sy = int(self.y)
    if sx < -40 or sx > SCREEN_W + 40:
        return
    if getattr(self, 'hurt_flash', 0) > 0 and (pyxel.frame_count // 2) % 2 == 0:
        return
    key = _v13_country_key(self)
    empire = bool(globals().get('_active_secret_game', None) and getattr(globals().get('_active_secret_game'), 'empire_mode', False))
    skin = self.skin if hasattr(self, 'skin') else {"body":C12,"cape":C8,"visor":C14,"form":"basic"}
    form = str(skin.get('form', 'basic'))
    body = int(skin.get('body', C12)); cape = int(skin.get('cape', C8)); visor = int(skin.get('visor', C14))
    trim = C7
    special_shape = form
    if empire:
        data = EMPIRE_FORM_V13.get(key, EMPIRE_FORM_V13['usa'])
        body, cape, trim, special_shape = data['body'], data['cape'], data['trim'], data['shape']
    if getattr(self, 'dash_timer', 0) > 0:
        for i in range(1, 5):
            ox = sx - self.dash_dir * i * 8
            col = trim if empire else C12
            pyxel.rect(ox + 5, sy + 4, 8, 14, col if i < 2 else C5)
            pyxel.pset(ox + 2, sy + 8 + i, C10)
            pyxel.pset(ox + 16, sy + 13 - i, C7)
    if getattr(self, 'invuln', 0) > 0 and (pyxel.frame_count // 4) % 2 == 0:
        pyxel.circb(sx + 8, sy + 11, 14, C10 if empire else C12)
    step = 0
    if getattr(self, 'on_ground', False) and abs(getattr(self, 'vx', 0)) > 0.1:
        step = 1 if (self.walk_frame // 5) % 2 == 0 else -1
    wave = 1 if (pyxel.frame_count // 5) % 2 == 0 else -1
    if special_shape in ('wings','eagle','dragon'):
        pyxel.tri(sx + 5, sy + 7, sx - 10, sy + 18 + wave, sx + 3, sy + 19, cape)
        pyxel.tri(sx + 11, sy + 7, sx + 26, sy + 18 - wave, sx + 13, sy + 19, cape)
    else:
        pyxel.tri(sx + 4, sy + 8, sx - 5, sy + 22 + wave, sx + 5, sy + 20, cape)
        pyxel.tri(sx + 12, sy + 8, sx + 21, sy + 22 - wave, sx + 11, sy + 20, cape)
    pole_x = sx + 20 if self.facing == 1 else sx - 6
    pyxel.line(pole_x, sy + 0, pole_x, sy + 24, C4)
    if self.facing == 1:
        (_v13_draw_banner if empire else draw_flag)(key, pole_x + 1, sy + 2, 17 if empire else 14, 10 if empire else 8, pyxel.frame_count * .2)
    else:
        (_v13_draw_banner if empire else draw_flag)(key, pole_x - 18, sy + 2, 17 if empire else 14, 10 if empire else 8, pyxel.frame_count * .2)
    pyxel.rect(sx + 1, sy + 5, 14, 14, C0)
    pyxel.rect(sx + 4, sy + 6, 8, 12, body)
    pyxel.rect(sx + 5, sy + 7, 6, 9, min(15, body + 1))
    pyxel.line(sx + 8, sy + 6, sx + 8, sy + 17, max(1, body - 1))
    pyxel.rect(sx + 1, sy + 7, 4, 4, trim)
    pyxel.rect(sx + 11, sy + 7, 4, 4, trim)
    pyxel.rect(sx + 3, sy - 1, 10, 7, C0)
    pyxel.rect(sx + 4, sy, 8, 5, body)
    pyxel.rect(sx + 3, sy + 1, 10, 3, body)
    pyxel.rect(sx + 4, sy + 2, 8, 2, visor)
    if special_shape == 'roman':
        pyxel.rect(sx + 2, sy - 3, 12, 2, trim); pyxel.line(sx + 8, sy - 8, sx + 8, sy - 3, C8)
        pyxel.line(sx + 5, sy - 7, sx + 11, sy - 7, C8)
    elif special_shape == 'turban':
        pyxel.circ(sx + 8, sy - 2, 6, C7); pyxel.circ(sx + 8, sy - 2, 4, body); pyxel.pset(sx + 12, sy - 4, C10)
    elif special_shape == 'dragon':
        pyxel.line(sx + 3, sy - 2, sx - 2, sy - 7, C8); pyxel.line(sx + 13, sy - 2, sx + 18, sy - 7, C8)
        pyxel.pset(sx + 8, sy - 5, C10)
    elif special_shape == 'spike':
        pyxel.tri(sx + 5, sy - 1, sx + 8, sy - 8, sx + 11, sy - 1, trim)
    elif special_shape == 'crown':
        pyxel.line(sx + 3, sy - 2, sx + 13, sy - 2, C10)
        for px in (4,8,12): pyxel.pset(sx + px, sy - 4, C10)
    elif special_shape == 'sun':
        for a in range(0, 360, 45):
            px = sx + 8 + int(math.cos(math.radians(a)) * 7)
            py = sy + 1 + int(math.sin(math.radians(a)) * 7)
            pyxel.line(sx + 8, sy + 1, px, py, C10)
    elif special_shape == 'fur':
        pyxel.rect(sx + 1, sy + 4, 14, 3, C7); pyxel.pset(sx + 3, sy + 3, C7); pyxel.pset(sx + 12, sy + 3, C7)
    elif special_shape == 'feather':
        pyxel.line(sx + 9, sy - 1, sx + 16, sy - 9, C3); pyxel.pset(sx + 15, sy - 10, C10)
    elif special_shape == 'hood':
        pyxel.tri(sx + 2, sy + 4, sx + 8, sy - 8, sx + 14, sy + 4, cape)
    elif special_shape == 'antenna':
        pyxel.line(sx + 6, sy - 1, sx + 3, sy - 8, C7); pyxel.line(sx + 10, sy - 1, sx + 14, sy - 8, C7)
    elif special_shape == 'plume':
        pyxel.line(sx + 8, sy - 2, sx + 8, sy - 10, cape); pyxel.pset(sx + 7, sy - 9, cape); pyxel.pset(sx + 9, sy - 8, cape)
    elif special_shape == 'halo':
        pyxel.circb(sx + 8, sy - 4, 5, C10)
    pyxel.line(sx + 3, sy + 10, sx - 1, sy + 14, max(1, body-1))
    pyxel.line(sx + 13, sy + 10, sx + 18, sy + 10, trim)
    pyxel.rect(sx + 17, sy + 9, 5, 2, C5)
    pyxel.line(sx + 6, sy + 18, sx + 5 + step, sy + 24, C5)
    pyxel.line(sx + 10, sy + 18, sx + 11 - step, sy + 24, C5)
    pyxel.rect(sx + 3 + step, sy + 24, 4, 2, C0)
    pyxel.rect(sx + 9 - step, sy + 24, 4, 2, C0)
    if getattr(self, 'shoot_anim', 0) > 0:
        mx = sx + 22 if self.facing == 1 else sx - 5
        pyxel.circ(mx, sy + 10, 3, C9); pyxel.circ(mx, sy + 10, 2, C10)
Player.draw = _draw_player_form_v13
_old_next_level_v13 = Game.next_level
def _v13_level_len(game):
    if game.is_multiplayer() and game.mp_mode_names[game.mp_mode_index] == "CAMPAIGN ROTATION":
        return 1500
    return LEVEL_DISTANCE
def _ensure_exit_gate_v13(game):
    if not hasattr(game, 'level_exit_ready'):
        game.level_exit_ready = False
    if not hasattr(game, 'level_exit_x'):
        game.level_exit_x = getattr(game, 'checkpoint_x', 40) + _v13_level_len(game)
    if not hasattr(game, '_allow_level_advance'):
        game._allow_level_advance = False
def _reset_exit_gate_v13(game):
    game.level_exit_ready = False
    game.level_exit_x = getattr(game, 'checkpoint_x', 40) + _v13_level_len(game)
    game._allow_level_advance = False
    for flag in ["final_bunker_active", "final_bunker_ready", "final_bunker_spawned", "awaiting_final_bunker", "boss_final_bunker_mode"]:
        if hasattr(game, flag):
            setattr(game, flag, False)
    if hasattr(game, 'final_bunker_x'):
        game.final_bunker_x = -999999
def _near_exit_bunker_v13(game):
    _ensure_exit_gate_v13(game)
    return getattr(game, 'level_exit_ready', False) and game.player.x + PLAYER_W / 2 >= game.level_exit_x + 16
def _next_level_v13(self):
    _ensure_exit_gate_v13(self)
    campaign_like = (self.is_campaign() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"))
    if campaign_like and getattr(self, 'state', '') == 'playing' and not getattr(self, '_allow_level_advance', False):
        if not _near_exit_bunker_v13(self):
            self.level_exit_ready = True
            self.level_exit_x = getattr(self, 'checkpoint_x', 40) + _v13_level_len(self)
            self.distance = min(getattr(self, 'distance', 0), _v13_level_len(self) - 18)
            if not hasattr(self, '_exit_gate_text_cd') or self._exit_gate_text_cd <= 0:
                self.add_text(self.player.x + 30, 38, "ENTER THE BUNKER", C14)
                self._exit_gate_text_cd = 60
            return
        self._allow_level_advance = True
    try:
        return _old_next_level_v13(self)
    finally:
        _reset_exit_gate_v13(self)
Game.next_level = _next_level_v13
_old_reset_run_v13 = Game.reset_run
def _reset_run_v13(self):
    _old_reset_run_v13(self)
    _reset_exit_gate_v13(self)
    try:
        _setup_tutorial_v13(self)
    except Exception:
        pass
Game.reset_run = _reset_run_v13
_old_update_playing_v13 = Game.update_playing
def _update_playing_v13(self):
    _ensure_exit_gate_v13(self)
    if hasattr(self, '_exit_gate_text_cd') and self._exit_gate_text_cd > 0:
        self._exit_gate_text_cd -= 1
    result = _old_update_playing_v13(self)
    if getattr(self, 'state', '') != 'playing':
        return result
    _ensure_exit_gate_v13(self)
    campaign_like = (self.is_campaign() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"))
    if campaign_like:
        if self.distance >= _v13_level_len(self) - 90 or getattr(self, 'boss_killed_this_level', False):
            self.level_exit_ready = True
            self.level_exit_x = getattr(self, 'checkpoint_x', 40) + _v13_level_len(self)
        if self.level_exit_ready and _near_exit_bunker_v13(self):
            self._allow_level_advance = True
            self.next_level()
    return result
Game.update_playing = _update_playing_v13
_old_draw_bunkers_v13 = Game.draw_bunkers
def _draw_bunkers_v13(self):
    try:
        sx = int(getattr(self, 'checkpoint_x', 40) - self.cam_x)
        gy = self.terrain.ground_y_at(getattr(self, 'checkpoint_x', 40), self.difficulty_value())
        if -70 <= sx <= SCREEN_W + 70:
            self.draw_bunker_style(sx, gy, self.current_country()["key"])
    except Exception:
        pass
    try:
        _ensure_exit_gate_v13(self)
        campaign_like = (self.is_campaign() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"))
        if campaign_like and (self.level_exit_ready or self.distance > _v13_level_len(self) - 500):
            ex = self.level_exit_x
            sx2 = int(ex - self.cam_x)
            gy2 = self.terrain.ground_y_at(ex, self.difficulty_value())
            if -90 <= sx2 <= SCREEN_W + 100:
                self.draw_bunker_style(sx2, gy2, self.current_country()["key"])
                pyxel.text(sx2 - 4, gy2 - 54, "LEVEL EXIT", C14)
                if (pyxel.frame_count // 12) % 2 == 0:
                    pyxel.text(sx2 - 8, gy2 - 44, "ENTER", C10)
    except Exception:
        pass
    try:
        if '_draw_secret_dev_bunker' in globals():
            _draw_secret_dev_bunker(self)
    except Exception:
        pass
Game.draw_bunkers = _draw_bunkers_v13
_old_boss_update_v13 = BossEnemy.update
def _boss_update_v13(self, game):
    return _old_boss_update_v13(self, game)
BossEnemy.update = _boss_update_v13
_old_boss_draw_v13 = BossEnemy.draw
def _boss_draw_v13(self, cam_x):
    _old_boss_draw_v13(self, cam_x)
    sx, sy = int(self.x - cam_x), int(self.y)
    hp_ratio = clamp(getattr(self, 'hp', 1) / max(1, getattr(self, 'max_hp', 1)), 0, 1)
    crack_col = C8 if hp_ratio < .5 else C10
    for i in range(5):
        if hp_ratio < 0.85 - i * .13:
            ox = sx + 18 + i * 9
            oy = sy + 18 + (i % 3) * 9
            pyxel.line(ox, oy, ox + 6, oy + 5, crack_col)
            pyxel.line(ox + 3, oy + 3, ox, oy + 8, C0)
    if hp_ratio < .55:
        pyxel.circ(sx + 38, sy + 31, 11, C8)
        pyxel.circb(sx + 38, sy + 31, 14 + (pyxel.frame_count // 5) % 3, C10)
        if pyxel.frame_count % 5 == 0:
            pyxel.pset(sx + random.randint(20, 56), sy + random.randint(8, 54), C7)
    if hp_ratio < .30:
        pyxel.text(sx + 12, sy - 18, "SYSTEM FAIL", C8 if (pyxel.frame_count//4)%2 else C10)
    bw = 72
    pyxel.rect(sx + 2, sy - 10, bw, 5, C0)
    pyxel.rect(sx + 2, sy - 10, int(bw * hp_ratio), 5, C8 if hp_ratio < .35 else C10)
    pyxel.rectb(sx + 2, sy - 10, bw, 5, C7)
BossEnemy.draw = _boss_draw_v13
_old_kill_enemy_v13 = Game.kill_enemy
def _kill_enemy_v13(self, enemy):
    was_boss = isinstance(enemy, BossEnemy)
    bx = getattr(enemy, 'x', self.player.x)
    by = getattr(enemy, 'y', 80)
    _old_kill_enemy_v13(self, enemy)
    if was_boss:
        self.boss_killed_this_level = True
        self.level_boss_spawned = True
        self.level_exit_ready = False
        self.boss_death_cinematic = max(getattr(self, 'boss_death_cinematic', 0), FPS * 3)
        self.boss_death_x = bx
        self.boss_death_y = by
        self.add_shake(40, 5)
        for _ in range(54):
            self.particles.append(Particle(bx + 38 + random.uniform(-34,34), by + 34 + random.uniform(-26,26), random.uniform(-3.5,3.5), random.uniform(-3,2), random.choice([C8,C9,C10,C7,C5]), random.randint(28,70), size=random.choice([1,1,2]), gravity=.045))
        for _ in range(5):
            self.effects.append(Explosion(bx + random.randint(10,68), by + random.randint(10,64), random.randint(14,28), "normal"))
        self.enemies = [e for e in self.enemies if not isinstance(e, BossEnemy)]
Game.kill_enemy = _kill_enemy_v13
_old_update_playing_boss_v13 = Game.update_playing
def _update_playing_boss_v13(self):
    if getattr(self, 'boss_death_cinematic', 0) > 0:
        self.boss_death_cinematic -= 1
        self.update_shake()
        for e in self.effects: e.update()
        for p in self.particles: p.update()
        self.effects = [e for e in self.effects if e.alive]
        self.particles = [p for p in self.particles if p.alive]
        if self.boss_death_cinematic <= 0:
            self.level_exit_ready = True
            self.level_exit_x = getattr(self, 'checkpoint_x', 40) + _v13_level_len(self)
            self.distance = max(getattr(self, 'distance', 0), _v13_level_len(self) - 160)
            self.add_text(self.player.x + 50, 42, "BUNKER OPEN", C14)
        return
    return _old_update_playing_boss_v13(self)
Game.update_playing = _update_playing_boss_v13
_old_draw_world_v13 = Game.draw_world
def _draw_world_v13(self):
    _old_draw_world_v13(self)
    if getattr(self, 'boss_death_cinematic', 0) > 0:
        t = getattr(self, 'boss_death_cinematic', 0)
        cx = int(getattr(self, 'boss_death_x', self.player.x) - self.cam_x + 38)
        cy = int(getattr(self, 'boss_death_y', 72) + 34)
        pyxel.camera(0,0)
        if (pyxel.frame_count // 3) % 2 == 0:
            pyxel.rect(0,0,SCREEN_W,SCREEN_H,C0)
        r = 18 + (FPS*3 - t) // 3
        pyxel.circb(cx, cy, min(80,r), C8)
        pyxel.circb(cx, cy, min(90,r+10), C10)
        pyxel.text(94, 38, "BOSS CORE MELTDOWN", C8 if (pyxel.frame_count//4)%2 else C10)
        pyxel.text(86, 154, "EXIT BUNKER UNLOCKING...", C14)
Game.draw_world = _draw_world_v13
def _setup_tutorial_v13(game):
    game.tutorial_step = 0
    game.tutorial_flags = {"jump":False,"move":False,"balloon":False,"rocket":False,"bomb":False,"dash":False,"pause":False}
    game.tutorial_balloon = {"x":210,"y":78,"alive":True}
    game.tutorial_drone = {"x":218,"y":70,"alive":True,"arrive":0}
    game.tutorial_turret = {"x":230,"alive":True}
    try:
        game.player.reset(58, game.difficulty_value())
        game.player.rockets = max(game.player.rockets, 3)
        game.player.bombs = max(game.player.bombs, 3)
        game.cam_x = 0
    except Exception:
        pass
_old_game_init_v13 = Game.__init__
_old_update_tutorial_v13 = Game.update_tutorial
def _update_tutorial_v13(self):
    if not hasattr(self, 'tutorial_flags'):
        _setup_tutorial_v13(self)
    if pyxel.btnp(pyxel.KEY_RETURN):
        self.state = "playing"; return
    if pyxel.btnp(pyxel.KEY_P):
        self.tutorial_flags["pause"] = True
        self.add_text(self.player.x + 10, 42, "PAUSE LEARNED", C14)
    old_x, old_y = self.player.x, self.player.y
    self.player.update(self.difficulty_value(), getattr(self, 'dev_mode', False))
    self.player.x = clamp(self.player.x, 8, 292)
    self.cam_x = 0
    if abs(self.player.x - old_x) > .4:
        self.tutorial_flags["move"] = True
    if pyxel.btnp(pyxel.KEY_W) or self.player.y < old_y - 1:
        self.tutorial_flags["jump"] = True
    if pyxel.btnp(pyxel.KEY_SPACE) or getattr(self.player, 'dash_timer', 0) > 0:
        self.tutorial_flags["dash"] = True
    self.handle_player_fire()
    for b in self.player_bullets: b.update()
    for r in self.rockets:
        r.update([])
    for b in self.bombs: b.update()
    for e in self.effects: e.update()
    bal = self.tutorial_balloon
    if bal.get('alive'):
        for b in self.player_bullets:
            if dist2(b.x, b.y, bal['x'], bal['y']) < 13*13:
                bal['alive'] = False; b.alive = False; self.tutorial_flags['balloon'] = True
                self.spawn_explosion(bal['x'], bal['y'], 10, 'muzzle')
                self.add_text(bal['x'], bal['y']-18, "BALLOON POPPED", C14)
    dr = self.tutorial_drone
    dr['arrive'] = min(60, dr.get('arrive',0)+1)
    if dr.get('alive'):
        for r in self.rockets:
            if dist2(r.x, r.y, dr['x'], dr['y']) < 18*18:
                dr['alive'] = False; r.alive = False; self.tutorial_flags['rocket'] = True
                self.spawn_explosion(dr['x'], dr['y'], 16, 'normal')
                self.add_text(dr['x']-12, dr['y']-20, "DRONE DOWN", C10)
    tu = self.tutorial_turret
    tx = tu['x']; ty = self.terrain.ground_y_at(tx, self.difficulty_value()) - 10
    if tu.get('alive'):
        for bomb in self.bombs:
            if abs(bomb.x - tx) < 20 and abs(bomb.y - ty) < 28:
                tu['alive'] = False; bomb.alive = False; self.tutorial_flags['bomb'] = True
                self.spawn_explosion(tx, ty, 18, 'normal')
                self.add_text(tx-20, ty-20, "TURRET DESTROYED", C10)
    self.player_bullets = [b for b in self.player_bullets if b.alive]
    self.rockets = [r for r in self.rockets if r.alive]
    self.bombs = [b for b in self.bombs if b.alive]
    self.effects = [e for e in self.effects if e.alive]
    order = ["jump","move","balloon","rocket","bomb","dash","pause"]
    while self.tutorial_step < len(order) and self.tutorial_flags.get(order[self.tutorial_step], False):
        self.tutorial_step += 1
    if self.tutorial_step >= len(order):
        self.add_text(self.player.x + 10, 42, "TRAINING COMPLETE", C14)
        if not hasattr(self, '_tutorial_done_timer'):
            self._tutorial_done_timer = 75
        self._tutorial_done_timer -= 1
        if self._tutorial_done_timer <= 0:
            self.state = "playing"
Game.update_tutorial = _update_tutorial_v13
def _draw_training_map_v13(game):
    pyxel.cls(C0)
    for i in range(0, SCREEN_W, 18):
        h = 18 + (i*7) % 34
        pyxel.rect(i, 132-h, 10, h, C5)
        if i % 36 == 0: pyxel.pset(i+5, 130-h, C8)
    game.terrain.draw(0, game.difficulty_value())
    pyxel.text(8, 6, _tutorial_instruction_v13(game), C7)
    pyxel.text(8, 16, "ENTER SKIP", C6)
    pyxel.text(8, 28, "ALL CONTROLS ARE ACTIVE", C12)
    labels = [("W JUMP","jump"),("A/D MOVE","move"),("ARROWS BALLOON","balloon"),("K ROCKET DRONE","rocket"),("J BOMB TURRET","bomb"),("SPACE DASH","dash"),("P PAUSE","pause")]
    y=42
    for label,flag in labels:
        col=C10 if game.tutorial_flags.get(flag) else (C14 if labels.index((label,flag))==game.tutorial_step else C5)
        pyxel.text(8,y,("OK " if game.tutorial_flags.get(flag) else "-- ")+label,col); y+=8
    bal = game.tutorial_balloon
    if bal.get('alive'):
        pyxel.circ(bal['x'], bal['y'], 8, C8); pyxel.circ(bal['x']-2, bal['y']-2, 3, C14); pyxel.line(bal['x'], bal['y']+8, bal['x'], bal['y']+22, C7)
        pyxel.text(bal['x']-28, bal['y']-20, "POP THE BALLOON", C14)
    dr = game.tutorial_drone
    if dr.get('alive'):
        dx = dr['x'] + max(0, 60-dr.get('arrive',0))*2
        pyxel.rect(dx-12, dr['y']-5, 24, 10, C5); pyxel.rectb(dx-12, dr['y']-5, 24, 10, C12)
        pyxel.circ(dx-16, dr['y'], 3, C7); pyxel.circ(dx+16, dr['y'], 3, C7)
        if game.tutorial_step <= 3:
            pyxel.text(170, 48, "DRONE WAITS: PRESS K", C10)
    tu = game.tutorial_turret
    if tu.get('alive'):
        tx=tu['x']; ty=game.terrain.ground_y_at(tx, game.difficulty_value())-10
        pyxel.rect(tx-8, ty, 16, 10, C4); pyxel.rectb(tx-8, ty, 16, 10, C8); pyxel.line(tx, ty, tx+12, ty-8, C7)
        if game.tutorial_step <= 4: pyxel.text(180, 126, "J = BOMB TURRET", C8)
    for obj_list in (game.player_bullets, game.rockets, game.bombs, game.effects, game.particles, game.texts):
        for o in obj_list:
            if hasattr(o,'draw'): o.draw(0)
    game.player.draw(0)
def _tutorial_instruction_v13(game):
    steps = [
        "1) PRESS W TO JUMP",
        "2) HOLD A OR D TO MOVE",
        "3) ARROW KEYS: POP THE BALLOON",
        "4) K FOR ROCKET: HIT THE DRONE",
        "5) J FOR BOMB: DESTROY THE GROUND TURRET",
        "6) SPACE TO DASH",
        "7) P IS PAUSE",
        "TRAINING COMPLETE",
    ]
    return steps[min(getattr(game,'tutorial_step',0), len(steps)-1)]
def _draw_tutorial_v13(self):
    if not hasattr(self, 'tutorial_flags'):
        _setup_tutorial_v13(self)
    _draw_training_map_v13(self)
Game.draw_tutorial = _draw_tutorial_v13
def _draw_empire_ascension_v13(self):
    pyxel.cls(C0)
    t = getattr(self, 'empire_anim_timer', getattr(self, 'intro_timer', 0))
    key = self.current_country()["key"]
    data = EMPIRE_FORM_V13.get(key, EMPIRE_FORM_V13['usa'])
    name = REAL_EMPIRE_NAMES.get(key, self.current_country()["name"] + " EMPIRE")
    for i in range(0, SCREEN_W, 16):
        pyxel.line(i, 0, (i + t//2) % SCREEN_W, SCREEN_H, C1 if i%32 else C5)
    pyxel.rect(16, 12, 288, 170, C0); pyxel.rectb(16, 12, 288, 170, C10)
    pyxel.text(72, 22, "EMPIRE ASCENSION SEQUENCE", C10)
    pyxel.text(62, 34, name[:34], C14)
    draw_country_outline(key, 48, 58, scale=2, color=C5, fill=False)
    for r in range(4):
        if t > r*35:
            pyxel.circb(92, 88, 12 + r*10 + (t//4)%4, C10 if r%2 else C14)
    pyxel.text(32, 142, "TERRITORY GROWS", C7)
    cx, cy = 160, 96
    pyxel.rectb(cx-32, cy-48, 64, 86, C12)
    pyxel.line(cx, cy-48, cx, cy+38, C5)
    for i in range(6):
        yy = cy - 40 + i*14
        pyxel.line(cx-28, yy, cx+28, yy + ((t+i*3)%9)-4, C12 if i%2 else C10)
    fake = self.player
    ox, oy = fake.x, fake.y
    fake.x, fake.y = cx-8, cy-8
    old_active = globals().get('_active_secret_game', None)
    globals()['_active_secret_game'] = self
    try:
        fake.draw(0)
    finally:
        globals()['_active_secret_game'] = old_active
        fake.x, fake.y = ox, oy
    pyxel.text(cx-48, cy+48, data['title'][:28], C14)
    pyxel.text(218, 62, "BOOSTS", C7)
    pyxel.text(212, 78, "SPEED +0.10", C10 if (t//8)%2 else C7)
    pyxel.text(212, 92, "JUMP  +0.14", C10 if (t//8)%2 else C7)
    pyxel.text(212, 106, "HP BAR = 1", C8)
    pyxel.text(206, 128, "DIFFICULTY CONTINUES", C14)
    _v13_draw_banner(key, 224, 144, 38, 22, t*.2)
    if t < 70: msg="OLD NATION BREAKS"
    elif t < 140: msg="COMMAND FORM CHANGES"
    elif t < 220: msg="SPEED AND JUMP CALIBRATED"
    elif t < 310: msg="EMPIRE DEPLOYMENT READY"
    else: msg="PRESS ENTER"
    pyxel.text(92, 168, msg, C8 if (pyxel.frame_count//4)%2 else C10)
Game.draw_empire_ascension = _draw_empire_ascension_v13
_old_update_empire_ascension_v13 = getattr(Game, 'update_empire_ascension', None)
def _update_empire_ascension_v13(self):
    self.empire_anim_timer = getattr(self, 'empire_anim_timer', 0) + 1
    self.intro_timer = self.empire_anim_timer
    if self.empire_anim_timer > FPS*6 and pyxel.btnp(pyxel.KEY_RETURN):
        try:
            _enter_empire_campaign(self, from_dev=False)
        except Exception:
            self.state='playing'
    elif self.empire_anim_timer > FPS*9:
        try:
            _enter_empire_campaign(self, from_dev=False)
        except Exception:
            self.state='playing'
Game.update_empire_ascension = _update_empire_ascension_v13
_old_start_ascension_v13 = globals().get('_start_empire_ascension')
def _start_empire_ascension_v13(game):
    game.empire_anim_timer = 0
    if _old_start_ascension_v13:
        try:
            _old_start_ascension_v13(game)
        except Exception:
            game.state = 'empire_ascension'
    else:
        game.state = 'empire_ascension'
globals()['_start_empire_ascension'] = _start_empire_ascension_v13
def _draw_order_complete_v13(game):
    t = getattr(game, 'order_complete_timer', 0)
    if t <= 0: return
    pulse = C14 if (pyxel.frame_count//3)%2 else C10
    y = 42 if t > 45 else 42 - (45-t)
    pyxel.rect(26, y, 268, 62, C0)
    pyxel.rectb(26, y, 268, 62, pulse)
    pyxel.rect(30, y+4, 260, 12, C1)
    pyxel.text(92, y+7, "BLUE RADIO // MISSION COMPLETE", C12)
    pyxel.text(54, y+26, str(getattr(game,'order_complete_title','FIELD ORDER'))[:32], C7)
    pyxel.text(54, y+38, str(getattr(game,'order_complete_reward','REWARD SENT'))[:34], C10)
    cx, cy = 246, y+36
    pyxel.circb(cx, cy, 15 + (pyxel.frame_count//4)%4, pulse)
    pyxel.circ(cx, cy, 9, C10)
    pyxel.text(cx-6, cy-2, "OK", C0)
_old_draw_hud_v13 = Game.draw_hud
def _draw_hud_v13(self):
    _old_draw_hud_v13(self)
    _draw_order_complete_v13(self)
Game.draw_hud = _draw_hud_v13
_old_update_v13 = Game.update
def _update_v13(self):
    globals()['_active_secret_game'] = self
    if getattr(self, 'state', '') == 'tutorial' and not hasattr(self, 'tutorial_flags'):
        _setup_tutorial_v13(self)
    if getattr(self, 'state', '') == 'empire_ascension':
        self.update_empire_ascension()
        return
    return _old_update_v13(self)
Game.update = _update_v13
FINAL_BUILD_TAG = "v13-polish"
def _ensure_v14_state(game):
    if not hasattr(game, 'earthquake_timer'):
        game.earthquake_timer = 0
    if not hasattr(game, 'earthquake_next_level'):
        base_level = int(getattr(game, 'level', 1))
        game.earthquake_next_level = base_level + random.choice([1, 2])
    if not hasattr(game, 'earthquake_trench_cd'):
        game.earthquake_trench_cd = 0
    if not hasattr(game, 'damage_flash_timer'):
        game.damage_flash_timer = 0
    if not hasattr(game, 'bunker_prompt_level_seen'):
        game.bunker_prompt_level_seen = -999
    if not hasattr(game, 'landmine_spawn_cd'):
        game.landmine_spawn_cd = FPS * 4
    if not hasattr(game.player, 'stun_timer'):
        game.player.stun_timer = 0
def _campaign_like_v14(game):
    return game.is_campaign() or (game.is_multiplayer() and game.mp_mode_names[game.mp_mode_index] == 'CAMPAIGN ROTATION')
def _level_len_v14(game):
    try:
        return _v13_level_len(game)
    except Exception:
        return 1500 if (game.is_multiplayer() and game.mp_mode_names[game.mp_mode_index] == 'CAMPAIGN ROTATION') else LEVEL_DISTANCE
_EXTRA_FIELD_ORDERS_V14 = [
    {'code':'BUNKER DASH', 'brief':'ADVANCE FAST WITHOUT A HIT', 'kind':'nohit_distance', 'goal':360, 'limit':48*FPS, 'color':C12},
    {'code':'TRENCH PAYDAY', 'brief':'GRAB COINS BEFORE STATIC FADES', 'kind':'coins', 'goal':22, 'limit':62*FPS, 'color':C10},
    {'code':'IRON CHAIN', 'brief':'KEEP A HIGH COMBO ALIVE', 'kind':'combo', 'goal':13, 'limit':54*FPS, 'color':C14},
    {'code':'AA SWEEP', 'brief':'CLEAR THE SKY AND GROUND', 'kind':'kills', 'goal':7, 'limit':50*FPS, 'color':C8},
    {'code':'BLACKOUT RUN', 'brief':'DODGE CLOSE FIRE', 'kind':'near', 'goal':4, 'limit':66*FPS, 'color':C15},
]
try:
    _existing_codes_v14 = {o.get('code') for o in FIELD_ORDER_POOL}
    for _order in _EXTRA_FIELD_ORDERS_V14:
        if _order['code'] not in _existing_codes_v14:
            FIELD_ORDER_POOL.append(_order)
except Exception:
    pass
_old_complete_field_order_v14 = globals().get('_complete_field_order')
def _complete_field_order_v14(game):
    if _old_complete_field_order_v14:
        _old_complete_field_order_v14(game)
    try:
        game.field_order_cooldown = random.randint(10 * FPS, 15 * FPS)
    except Exception:
        pass
globals()['_complete_field_order'] = _complete_field_order_v14
_old_fail_field_order_v14 = globals().get('_fail_field_order')
def _fail_field_order_v14(game, reason='FAILED'):
    if _old_fail_field_order_v14:
        _old_fail_field_order_v14(game, reason)
    try:
        game.field_order_cooldown = random.randint(9 * FPS, 14 * FPS)
    except Exception:
        pass
globals()['_fail_field_order'] = _fail_field_order_v14
_old_missile_chance_v14 = Game.missile_spawn_chance
_old_drone_chance_v14 = Game.drone_spawn_chance
_old_turret_chance_v14 = Game.turret_spawn_chance
_old_bomber_chance_v14 = Game.bomber_spawn_chance
def _missile_spawn_chance_v14(self):
    return _old_missile_chance_v14(self) * 0.67
def _drone_spawn_chance_v14(self):
    return _old_drone_chance_v14(self) * 0.67
def _turret_spawn_chance_v14(self):
    return _old_turret_chance_v14(self) * 0.67
def _bomber_spawn_chance_v14(self):
    d = self.difficulty_value()
    if d < 2.4:
        return 0
    base = _old_bomber_chance_v14(self)
    return min(max(base * 2.8, 0.0007 + d * 0.00012), 0.0065)
Game.missile_spawn_chance = _missile_spawn_chance_v14
Game.drone_spawn_chance = _drone_spawn_chance_v14
Game.turret_spawn_chance = _turret_spawn_chance_v14
Game.bomber_spawn_chance = _bomber_spawn_chance_v14
_old_bomber_init_v14 = BomberEnemy.__init__
def _bomber_init_v14(self, x, y, direction, drop_x):
    _old_bomber_init_v14(self, x, y, direction, drop_x)
    self.drop_cd = FPS * 3
    self.warning_phase = False
    self.nuke_countdown_started = False
BomberEnemy.__init__ = _bomber_init_v14
def _bomber_update_v14(self, game):
    self.anim += 1
    self.x += self.vx
    self.update_flash()
    if not self.has_dropped:
        if self.warning_phase or abs((self.x + self.w // 2) - self.drop_x) < 135:
            self.warning_phase = True
            self.nuke_countdown_started = True
            self.drop_cd -= 1
            if self.drop_cd <= 0:
                self.has_dropped = True
                bomb = HeavyBombEnemy(self.x + self.w // 2 - 9, self.y + 24, 2.25)
                bomb.nuke_trench = True
                game.enemies.append(bomb)
                game.add_text(bomb.x - 12, bomb.y + 14, 'NUKE DROP', C8)
    if self.x < game.cam_x - 360 or self.x > game.cam_x + SCREEN_W + 360:
        self.alive = False
BomberEnemy.update = _bomber_update_v14
_old_bomber_draw_v14 = BomberEnemy.draw
def _bomber_draw_v14(self, cam_x):
    _old_bomber_draw_v14(self, cam_x)
    if getattr(self, 'warning_phase', False) and not getattr(self, 'has_dropped', False):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        n = max(1, int(math.ceil(max(0, self.drop_cd) / FPS)))
        pyxel.rect(sx + 56, sy - 27, 42, 12, C0)
        pyxel.rectb(sx + 56, sy - 27, 42, 12, C8 if (pyxel.frame_count // 5) % 2 else C10)
        pyxel.text(sx + 61, sy - 24, 'NUKE ' + str(n), C7)
BomberEnemy.draw = _bomber_draw_v14
_old_terrain_add_crater_v14 = Terrain.add_crater
def _add_crater_v14(self, x, radius):
    if radius >= 26:
        radius = max(radius, 58)
        self.craters.append({
            'x': x,
            'r': radius,
            'gap_r': radius * 0.56,
            'depth': int(radius * 0.92),
        })
        if len(self.craters) > 42:
            self.craters.pop(0)
        return
    return _old_terrain_add_crater_v14(self, x, radius)
Terrain.add_crater = _add_crater_v14
def _start_earthquake_v14(game):
    _ensure_v14_state(game)
    if game.earthquake_timer > 0:
        return
    game.earthquake_timer = FPS * 4
    game.earthquake_trench_cd = 1
    game.earthquake_next_level = int(getattr(game, 'level', 1)) + random.choice([1, 2])
    game.add_text(game.player.x + 26, 38, 'EARTHQUAKE', C7)
    game.add_shake(FPS * 4, 8)
    sfx(0, 7)
def _update_earthquake_v14(game):
    _ensure_v14_state(game)
    if not _campaign_like_v14(game) or getattr(game, 'state', '') != 'playing':
        return
    level_len = _level_len_v14(game)
    if game.earthquake_timer <= 0:
        if int(getattr(game, 'level', 1)) >= int(getattr(game, 'earthquake_next_level', 2)) and 0.24 < game.distance / max(1, level_len) < 0.78:
            _start_earthquake_v14(game)
        return
    game.earthquake_timer -= 1
    game.add_shake(2, 9)
    game.earthquake_trench_cd -= 1
    if game.earthquake_trench_cd <= 0:
        game.earthquake_trench_cd = random.randint(11, 20)
        for _ in range(random.randint(1, 3)):
            tx = game.player.x + random.randint(60, 420)
            tx += random.choice([-35, 0, 35])
            gy = game.terrain.base_ground_y_at(tx, game.difficulty_value())
            game.terrain.add_crater(tx, random.randint(30, 46))
            game.spawn_explosion(tx, gy, random.randint(10, 18), 'normal')
            for _p in range(10):
                game.particles.append(Particle(tx, gy, random.uniform(-2.2, 2.2), random.uniform(-2.8, -0.2), random.choice([C0, C7, C6, C5]), random.randint(14, 34), size=random.choice([1, 1, 2]), gravity=.08))
def _draw_earthquake_overlay_v14(game):
    if getattr(game, 'earthquake_timer', 0) <= 0:
        return
    pyxel.camera(0, 0)
    phase = pyxel.frame_count + game.earthquake_timer
    if random.random() < 0.34 or phase % 7 in (0, 3):
        pyxel.rect(0, 0, SCREEN_W, SCREEN_H, C7 if phase % 4 == 0 else C0)
    for i in range(0, SCREEN_W, random.choice([9, 13, 17])):
        if random.random() < 0.32:
            col = C7 if random.random() < 0.5 else C0
            pyxel.rect(i, 0, random.randint(1, 4), SCREEN_H, col)
    pyxel.text(104, 86, 'EARTHQUAKE', C0 if phase % 4 == 0 else C7)
    pyxel.text(78, 98, 'JUMP THE NEW TRENCHES', C0 if phase % 4 == 0 else C7)
class LandmineObstacle(Enemy):
    def __init__(self, x, y):
        super().__init__(x, y - 5, 16, 6, 1, 8)
        self.blink = random.randint(0, 60)
    def update(self, game):
        self.blink += 1
        self.y = game.terrain.ground_y_at(self.x + 8, game.difficulty_value()) - 5
        if self.x < game.cam_x - 120:
            self.alive = False
        self.update_flash()
    def draw(self, cam_x):
        sx = int(self.x - cam_x)
        sy = int(self.y)
        body = C7 if self.flash > 0 else C4
        pyxel.rect(sx, sy + 2, 16, 4, body)
        pyxel.rectb(sx, sy + 1, 16, 5, C0)
        pyxel.pset(sx + 4, sy + 1, C8 if (self.blink // 8) % 2 == 0 else C10)
        pyxel.pset(sx + 11, sy + 1, C8 if (self.blink // 8) % 2 == 0 else C10)
def _maybe_spawn_landmine_v14(game):
    _ensure_v14_state(game)
    if getattr(game, 'boss_death_cinematic', 0) > 0:
        return
    if any(isinstance(e, BossEnemy) for e in game.enemies):
        return
    game.landmine_spawn_cd -= 1
    if game.landmine_spawn_cd > 0:
        return
    game.landmine_spawn_cd = random.randint(FPS * 5, FPS * 9)
    if game.difficulty_value() < 2.0 or random.random() > 0.55:
        return
    tx = game.player.x + random.randint(170, 420)
    if game.terrain.is_gap_at(tx + 8, game.difficulty_value()):
        return
    gy = game.terrain.ground_y_at(tx + 8, game.difficulty_value())
    game.enemies.append(LandmineObstacle(tx, gy))
_old_player_update_v14 = Player.update
def _player_update_v14(self, difficulty, dev_mode=False):
    if getattr(self, 'stun_timer', 0) > 0 and not dev_mode:
        self.stun_timer -= 1
        self.vx = 0
        self.vy = 0
        self.dash_timer = 0
        self.walk_frame = 0
        self.shoot_anim = 0
        if self.invuln > 0:
            self.invuln -= 1
        if self.shot_cd > 0:
            self.shot_cd -= 1
        if self.hurt_flash > 0:
            self.hurt_flash -= 1
        if self.combo_timer > 0:
            self.combo_timer -= 1
            if self.combo_timer <= 0:
                self.combo = 0
        return
    return _old_player_update_v14(self, difficulty, dev_mode)
Player.update = _player_update_v14
_old_handle_player_fire_v14 = Game.handle_player_fire
def _handle_player_fire_v14(self):
    if getattr(self.player, 'stun_timer', 0) > 0:
        return
    return _old_handle_player_fire_v14(self)
Game.handle_player_fire = _handle_player_fire_v14
_old_handle_collisions_v14 = Game.handle_collisions
def _handle_collisions_v14(self):
    _ensure_v14_state(self)
    px, py, pw, ph = self.player.hurtbox()
    for e in list(self.enemies):
        if isinstance(e, LandmineObstacle) and e.alive and rects_overlap(px, py, pw, ph, e.x, e.y, e.w, e.h):
            e.alive = False
            self.player.stun_timer = max(getattr(self.player, 'stun_timer', 0), FPS * 2)
            self.player.vx = 0
            self.player.vy = 0
            self.player.hurt_flash = max(getattr(self.player, 'hurt_flash', 0), 8)
            self.spawn_explosion(e.x + 8, e.y + 3, 12, 'muzzle')
            self.add_shake(14, 4)
            self.add_text(self.player.x - 4, self.player.y - 12, 'STUNNED 2s', C14)
            sfx(1, 1)
    return _old_handle_collisions_v14(self)
Game.handle_collisions = _handle_collisions_v14
_old_player_hit_v14 = Game.player_hit
def _player_hit_v14(self, dmg=1):
    _ensure_v14_state(self)
    before_hp = getattr(self.player, 'hp', 0)
    result = _old_player_hit_v14(self, dmg)
    if not getattr(self, 'dev_mode', False) and getattr(self.player, 'hp', 0) < before_hp:
        self.damage_flash_timer = max(getattr(self, 'damage_flash_timer', 0), 9)
        self.add_shake(12, 4)
    return result
Game.player_hit = _player_hit_v14
def _draw_damage_flash_v14(game):
    if getattr(game, 'damage_flash_timer', 0) <= 0:
        return
    pyxel.camera(0, 0)
    if game.damage_flash_timer > 4 or (pyxel.frame_count // 2) % 2 == 0:
        pyxel.rect(0, 0, SCREEN_W, SCREEN_H, C0)
    pyxel.text(140, 92, 'HIT', C8 if (pyxel.frame_count // 2) % 2 else C7)
def _empire_bullet_style_v14(key):
    return {
        'italy': 'roman_spear',
        'germany': 'yellow_gas',
        'japan': 'shuriken',
        'china': 'dragon_fire',
        'russia': 'ice_shard',
        'uk': 'crown_round',
        'usa': 'eagle_star',
        'france': 'eagle_star',
        'turkiye': 'crescent',
    }.get(key, 'empire_bolt')
_old_player_fire_v14 = Player.fire
def _player_fire_v14(self):
    bullets, effects = _old_player_fire_v14(self)
    game = globals().get('_active_secret_game', None)
    if game is not None and getattr(game, 'empire_mode', False):
        key = _v13_country_key(game)
        style = _empire_bullet_style_v14(key)
        for b in bullets:
            b.empire_style = style
            b.empire_country = key
            if style == 'yellow_gas':
                b.color = C10
                b.radius = 3
                b.dmg = max(b.dmg, 1)
                b.life = max(b.life, 105)
            elif style == 'roman_spear':
                b.color = C7
                b.radius = 1
                b.dmg += 1
            elif style == 'ice_shard':
                b.color = C12
            elif style == 'dragon_fire':
                b.color = C8
            elif style == 'crescent':
                b.color = C10
    return bullets, effects
Player.fire = _player_fire_v14
_old_bullet_draw_v14 = Bullet.draw
def _bullet_draw_v14(self, cam_x):
    style = getattr(self, 'empire_style', None)
    if not style:
        return _old_bullet_draw_v14(self, cam_x)
    sx1 = int(self.px - cam_x)
    sy1 = int(self.py)
    sx = int(self.x - cam_x)
    sy = int(self.y)
    ang = math.atan2(self.y - self.py, self.x - self.px)
    dx = int(math.cos(ang) * 7)
    dy = int(math.sin(ang) * 7)
    if style == 'roman_spear':
        pyxel.line(sx - dx * 2, sy - dy * 2, sx + dx, sy + dy, C7)
        pyxel.line(sx - dx, sy - dy, sx + dx, sy + dy, C10)
        pyxel.tri(sx + dx, sy + dy, sx + dx - dy // 2, sy + dy + dx // 2, sx + dx + dy // 2, sy + dy - dx // 2, C8)
    elif style == 'yellow_gas':
        for ox, oy, rr in [(-3, 0, 3), (2, -2, 4), (5, 2, 3)]:
            pyxel.circb(sx + ox, sy + oy, rr, C10)
            if (pyxel.frame_count + ox) % 3 == 0:
                pyxel.pset(sx + ox, sy + oy, C7)
        pyxel.line(sx1, sy1, sx, sy, C10)
    elif style == 'shuriken':
        pyxel.line(sx - 4, sy, sx + 4, sy, C7)
        pyxel.line(sx, sy - 4, sx, sy + 4, C7)
        pyxel.line(sx - 3, sy - 3, sx + 3, sy + 3, C12)
        pyxel.line(sx - 3, sy + 3, sx + 3, sy - 3, C12)
    elif style == 'dragon_fire':
        pyxel.circ(sx, sy, 3, C8)
        pyxel.circ(sx - dx // 2, sy - dy // 2, 2, C10)
        pyxel.pset(sx, sy, C7)
    elif style == 'ice_shard':
        pyxel.tri(sx + dx, sy + dy, sx - dy, sy + dx, sx + dy, sy - dx, C12)
        pyxel.pset(sx, sy, C7)
    elif style == 'crown_round':
        pyxel.circ(sx, sy, 3, C10)
        pyxel.pset(sx - 2, sy - 3, C7); pyxel.pset(sx, sy - 4, C7); pyxel.pset(sx + 2, sy - 3, C7)
    elif style == 'eagle_star':
        pyxel.line(sx - 4, sy, sx + 4, sy, C7)
        pyxel.line(sx, sy - 4, sx, sy + 4, C7)
        pyxel.pset(sx - 2, sy - 2, C10); pyxel.pset(sx + 2, sy - 2, C10); pyxel.pset(sx, sy + 3, C10)
    elif style == 'crescent':
        pyxel.circ(sx, sy, 4, C10)
        pyxel.circ(sx + 2, sy, 4, C0)
        pyxel.pset(sx + 5, sy - 1, C7)
    else:
        pyxel.line(sx1, sy1, sx, sy, self.color)
        pyxel.circ(sx, sy, self.radius + 1, C10)
        pyxel.pset(sx, sy, C7)
Bullet.draw = _bullet_draw_v14
_old_add_text_v14 = Game.add_text
def _add_text_v14(self, x, y, text, color=C7):
    msg = str(text).upper().strip()
    if 'ENTER' in msg and 'BUNKER' in msg:
        _ensure_v14_state(self)
        if getattr(self, 'bunker_prompt_level_seen', -999) == int(getattr(self, 'level', 1)):
            return
        self.bunker_prompt_level_seen = int(getattr(self, 'level', 1))
        text = 'BUNKER OPEN'
    return _old_add_text_v14(self, x, y, text, color)
Game.add_text = _add_text_v14
def _draw_bunkers_v14(self):
    if not _campaign_like_v14(self):
        try:
            if '_draw_secret_dev_bunker' in globals():
                _draw_secret_dev_bunker(self)
        except Exception:
            pass
        return
    try:
        _ensure_exit_gate_v13(self)
    except Exception:
        pass
    level_len = _level_len_v14(self)
    start_x = getattr(self, 'checkpoint_x', 40) - 28
    gy = self.terrain.ground_y_at(start_x + 18, self.difficulty_value())
    if -80 <= start_x - self.cam_x <= SCREEN_W + 80:
        self.draw_bunker_style(start_x, gy, self.current_country()['key'])
    exit_x = getattr(self, 'level_exit_x', getattr(self, 'checkpoint_x', 40) + level_len)
    ready = bool(getattr(self, 'level_exit_ready', False) or getattr(self, 'distance', 0) > level_len - 620 or getattr(self, 'boss_killed_this_level', False))
    if ready:
        gy2 = self.terrain.ground_y_at(exit_x + 18, self.difficulty_value())
        sx2 = int(exit_x - self.cam_x)
        if -100 <= sx2 <= SCREEN_W + 120:
            self.draw_bunker_style(exit_x, gy2, self.current_country()['key'])
            pyxel.rect(sx2 - 8, gy2 - 58, 58, 17, C0)
            pyxel.rectb(sx2 - 8, gy2 - 58, 58, 17, C14)
            pyxel.text(sx2 - 4, gy2 - 54, 'NEXT LVL', C14)
            if (pyxel.frame_count // 12) % 2 == 0:
                pyxel.text(sx2 + 4, gy2 - 46, 'ENTER', C10)
    try:
        if '_draw_secret_dev_bunker' in globals():
            _draw_secret_dev_bunker(self)
    except Exception:
        pass
Game.draw_bunkers = _draw_bunkers_v14
_old_reset_run_v14 = Game.reset_run
def _reset_run_v14(self):
    _old_reset_run_v14(self)
    _ensure_v14_state(self)
    self.earthquake_timer = 0
    self.earthquake_next_level = int(getattr(self, 'level', 1)) + random.choice([1, 2])
    self.damage_flash_timer = 0
    self.bunker_prompt_level_seen = -999
    self.landmine_spawn_cd = FPS * 4
    if hasattr(self, 'field_order_cooldown'):
        self.field_order_cooldown = max(getattr(self, 'field_order_cooldown', 0), random.randint(8 * FPS, 12 * FPS))
Game.reset_run = _reset_run_v14
_old_next_level_v14 = Game.next_level
def _next_level_v14(self):
    result = _old_next_level_v14(self)
    try:
        _ensure_v14_state(self)
        self.earthquake_timer = 0
        self.bunker_prompt_level_seen = -999
        self.landmine_spawn_cd = FPS * 3
        if hasattr(self, 'field_order_cooldown'):
            self.field_order_cooldown = max(getattr(self, 'field_order_cooldown', 0), random.randint(8 * FPS, 13 * FPS))
    except Exception:
        pass
    return result
Game.next_level = _next_level_v14
_old_update_playing_v14 = Game.update_playing
def _update_playing_v14(self):
    _ensure_v14_state(self)
    result = _old_update_playing_v14(self)
    if getattr(self, 'state', '') == 'playing':
        _update_earthquake_v14(self)
        _maybe_spawn_landmine_v14(self)
    if getattr(self, 'damage_flash_timer', 0) > 0:
        self.damage_flash_timer -= 1
    return result
Game.update_playing = _update_playing_v14
_old_draw_world_v14 = Game.draw_world
def _draw_world_v14(self):
    _old_draw_world_v14(self)
    _draw_earthquake_overlay_v14(self)
    _draw_damage_flash_v14(self)
    if getattr(self.player, 'stun_timer', 0) > 0:
        pyxel.camera(0, 0)
        pyxel.rect(102, 25, 116, 14, C0)
        pyxel.rectb(102, 25, 116, 14, C14)
        pyxel.text(110, 29, 'LANDMINE STUN ' + str((self.player.stun_timer + FPS - 1) // FPS) + 's', C14)
Game.draw_world = _draw_world_v14
FINAL_BUILD_TAG = "v14-request-balance"
CAMPAIGN_LEVELS = 6
EMPIRE_CAMPAIGN_LEVELS = 14
EMPIRE_TOTAL_LEVELS = CAMPAIGN_LEVELS + EMPIRE_CAMPAIGN_LEVELS
FINAL_BOSS_LEVELS_NORMAL = {3, 6}
FINAL_BOSS_LEVELS_EMPIRE = {9, 12, 16, 20}
def _difficulty_value_v15(self):
    campaign_like = self.is_campaign() or (
        self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"
    )
    if campaign_like:
        lvl = int(getattr(self, "level", 1))
        pr = self.progress_ratio()
        if lvl <= CAMPAIGN_LEVELS:
            return 1.12 + lvl * 0.74 + pr * 1.18
        return 5.95 + (lvl - CAMPAIGN_LEVELS) * 0.43 + pr * 1.18
    return 0.95 + self.survival_frames / 940.0
Game.difficulty_value = _difficulty_value_v15
def _danger_scale_v15(self):
    campaign_like = self.is_campaign() or (
        self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == "CAMPAIGN ROTATION"
    )
    if campaign_like:
        lvl = int(getattr(self, "level", 1))
        pr = self.progress_ratio()
        if lvl <= CAMPAIGN_LEVELS:
            return 0.92 + (lvl - 1) * 0.18 + pr * 0.82
        return 1.92 + (lvl - CAMPAIGN_LEVELS) * 0.13 + pr * 0.80
    return 0.95 + self.survival_frames / 840.0
Game.danger_scale = _danger_scale_v15
_old_terrain_reset_v15 = Terrain.reset
def _terrain_reset_v15(self):
    _old_terrain_reset_v15(self)
    self.uplifts = []
Terrain.reset = _terrain_reset_v15
_old_terrain_ground_v15 = Terrain.ground_y_at
def _terrain_ground_y_at_v15(self, x, difficulty):
    y = _old_terrain_ground_v15(self, x, difficulty)
    for u in getattr(self, 'uplifts', []):
        dx = abs(x - u['x'])
        r = max(1, u['r'])
        if dx < r:
            t = dx / r
            y -= int((1 - t * t) * u['height'])
    return y
Terrain.ground_y_at = _terrain_ground_y_at_v15
_old_terrain_draw_v15 = Terrain.draw
def _terrain_draw_v15(self, cam_x, difficulty):
    _old_terrain_draw_v15(self, cam_x, difficulty)
    for u in getattr(self, 'uplifts', []):
        sx = int(u['x'] - cam_x)
        r = int(u['r'])
        if sx < -r - 20 or sx > SCREEN_W + r + 20:
            continue
        left = max(0, sx - r)
        right = min(SCREEN_W - 1, sx + r)
        for px in range(left, right, 4):
            wx = cam_x + px
            gy = self.ground_y_at(wx, difficulty)
            pyxel.line(px, gy, min(SCREEN_W - 1, px + 2), gy - 1, C10)
            if (px + pyxel.frame_count) % 16 < 5:
                pyxel.pset(px, gy - 2, C7)
Terrain.draw = _terrain_draw_v15
def _terrain_add_jumpable_trench_v15(terrain, x, radius=None):
    if radius is None:
        radius = random.randint(20, 28)
    terrain.craters.append({
        'x': x,
        'r': radius,
        'gap_r': random.randint(8, 14),
        'depth': random.randint(18, 30),
    })
    if len(terrain.craters) > 48:
        terrain.craters.pop(0)
def _terrain_add_uplift_v15(terrain, x):
    if not hasattr(terrain, 'uplifts'):
        terrain.uplifts = []
    terrain.uplifts.append({
        'x': x,
        'r': random.randint(32, 68),
        'height': random.randint(9, 24),
    })
    if len(terrain.uplifts) > 26:
        terrain.uplifts.pop(0)
def _start_earthquake_v15(game):
    _ensure_v14_state(game)
    if game.earthquake_timer > 0:
        return
    game.earthquake_timer = random.randint(FPS * 3, FPS * 5)
    game.earthquake_trench_cd = 1
    game.earthquake_recent_x = []
    game.earthquake_next_level = int(getattr(game, 'level', 1)) + random.choice([1, 2])
    game.add_text(game.player.x + 30, 38, 'EARTHQUAKE - JUMP CRACKS', C14)
    game.add_shake(game.earthquake_timer, 14)
    sfx(0, 7)
globals()['_start_earthquake_v14'] = _start_earthquake_v15
def _pick_quake_x_v15(game):
    recent = getattr(game, 'earthquake_recent_x', [])
    tries = 0
    while tries < 18:
        tries += 1
        tx = game.player.x + random.randint(55, 540) + random.choice([-70, -25, 0, 35, 90])
        if abs(tx - game.player.x) < 50:
            continue
        blocked = False
        for c in getattr(game.terrain, 'craters', []):
            if abs(tx - c.get('x', 0)) < c.get('r', 0) + 62:
                blocked = True
                break
        for rx in recent[-9:]:
            if abs(tx - rx) < 78:
                blocked = True
                break
        if blocked:
            continue
        return tx
    return game.player.x + random.randint(120, 460)
def _update_earthquake_v15(game):
    _ensure_v14_state(game)
    if not _campaign_like_v14(game) or getattr(game, 'state', '') != 'playing':
        return
    level_len = _level_len_v14(game)
    if game.earthquake_timer <= 0:
        if int(getattr(game, 'level', 1)) >= int(getattr(game, 'earthquake_next_level', 2)) and 0.20 < game.distance / max(1, level_len) < 0.80:
            _start_earthquake_v15(game)
        return
    game.earthquake_timer -= 1
    game.add_shake(2, random.randint(12, 19))
    game.earthquake_trench_cd -= 1
    if game.earthquake_trench_cd <= 0:
        game.earthquake_trench_cd = random.randint(13, 27)
        tx = _pick_quake_x_v15(game)
        game.earthquake_recent_x = (getattr(game, 'earthquake_recent_x', []) + [tx])[-12:]
        gy = game.terrain.base_ground_y_at(tx, game.difficulty_value())
        if random.random() < 0.68:
            _terrain_add_jumpable_trench_v15(game.terrain, tx)
            game.spawn_explosion(tx, gy, random.randint(10, 18), 'normal')
            for _p in range(12):
                game.particles.append(Particle(tx, gy, random.uniform(-2.8, 2.8), random.uniform(-3.4, -0.2), random.choice([C4, C5, C6, C7, C10]), random.randint(16, 42), size=random.choice([1, 1, 2]), gravity=.10))
        else:
            _terrain_add_uplift_v15(game.terrain, tx)
            game.spawn_explosion(tx, gy - 8, random.randint(6, 12), 'muzzle')
            for _p in range(10):
                game.particles.append(Particle(tx, gy - 6, random.uniform(-1.8, 1.8), random.uniform(-2.8, -0.4), random.choice([C3, C4, C5, C10]), random.randint(15, 34), size=1, gravity=.09))
globals()['_update_earthquake_v14'] = _update_earthquake_v15
def _draw_earthquake_overlay_v15(game):
    if getattr(game, 'earthquake_timer', 0) <= 0:
        return
    pyxel.camera(0, 0)
    if (pyxel.frame_count // 5) % 2 == 0:
        pyxel.rect(94, 7, 132, 13, C0)
        pyxel.rectb(94, 7, 132, 13, C14)
        pyxel.text(100, 11, 'EARTHQUAKE: JUMP CRACKS', C14)
globals()['_draw_earthquake_overlay_v14'] = _draw_earthquake_overlay_v15
_old_add_text_v15 = Game.add_text
def _add_text_v15(self, x, y, text, color=C7):
    msg = str(text).upper().strip()
    if ('BUNKER' in msg) or ('LEVEL EXIT' in msg) or ('ENTER BUNKER' in msg) or ('BUNKER OPEN' in msg):
        return
    return _old_add_text_v15(self, x, y, text, color)
Game.add_text = _add_text_v15
def _empire_bullet_style_v15(key):
    return {
        'china': 'dragon_red_yellow',
        'usa': 'eagle',
        'uk': 'royal_cannonball',
        'italy': 'italy_random',
        'japan': 'samurai_star',
        'france': 'baguette',
        'brazil': 'piranha_swarm',
        'turkiye': 'scimitar',
        'russia': 'bear_head',
        'germany': 'thick_yellow_gas',
    }.get(key, 'empire_flag_bolt')
_ITALY_BULLETS_V15 = ['laurel_spear', 'legionary_pilum', 'wolf_spirit', 'gladiator_net']
_old_player_fire_v15 = Player.fire
def _player_fire_v15(self):
    bullets, effects = _old_player_fire_v15(self)
    game = globals().get('_active_secret_game', None)
    if game is not None and getattr(game, 'empire_mode', False):
        key = _v13_country_key(game)
        style = _empire_bullet_style_v15(key)
        if style == 'italy_random':
            game.empire_shot_counter = getattr(game, 'empire_shot_counter', -1) + 1
            style = _ITALY_BULLETS_V15[game.empire_shot_counter % len(_ITALY_BULLETS_V15)]
        for i, b in enumerate(bullets):
            b.empire_style = style
            b.empire_country = key
            b.empire_seed = random.randint(0, 9999) + i * 31
            if style in ('dragon_red_yellow', 'thick_yellow_gas', 'baguette'):
                b.radius = max(b.radius, 3)
                b.life = max(b.life, 100)
            if style in ('laurel_spear', 'legionary_pilum'):
                b.radius = 1
                b.dmg += 1
            if style == 'gladiator_net':
                b.life = max(b.life, 95)
            if style == 'piranha_swarm':
                b.radius = 4
    return bullets, effects
Player.fire = _player_fire_v15
_old_bullet_update_v15 = Bullet.update
def _bullet_update_v15(self):
    _old_bullet_update_v15(self)
    style = getattr(self, 'empire_style', '')
    if style == 'piranha_swarm' and self.alive:
        self.y += math.sin((pyxel.frame_count + getattr(self, 'empire_seed', 0)) * 0.34) * 0.45
    elif style == 'wolf_spirit' and self.alive:
        self.y += math.sin((pyxel.frame_count + getattr(self, 'empire_seed', 0)) * 0.22) * 0.28
    elif style == 'thick_yellow_gas' and self.alive:
        self.vx *= 0.995
Bullet.update = _bullet_update_v15
_old_bullet_draw_v15 = Bullet.draw
def _bullet_draw_v15(self, cam_x):
    style = getattr(self, 'empire_style', None)
    if not style:
        return _old_bullet_draw_v15(self, cam_x)
    sx1, sy1 = int(self.px - cam_x), int(self.py)
    sx, sy = int(self.x - cam_x), int(self.y)
    ang = math.atan2(self.y - self.py, self.x - self.px)
    dx = int(math.cos(ang) * 7)
    dy = int(math.sin(ang) * 7)
    seed = getattr(self, 'empire_seed', 0)
    if style == 'dragon_red_yellow':
        for i in range(5):
            ox = -dx * i // 2 + int(math.sin((pyxel.frame_count + seed + i * 7) * .28) * 3)
            oy = -dy * i // 2 + int(math.cos((pyxel.frame_count + seed + i * 5) * .25) * 3)
            pyxel.circ(sx + ox, sy + oy, max(1, 4 - i // 2), C8 if i % 2 == 0 else C10)
        pyxel.tri(sx + dx, sy + dy, sx + dx - dy, sy + dy + dx, sx + dx + dy, sy + dy - dx, C10)
        pyxel.pset(sx + dx, sy + dy, C7)
    elif style == 'eagle':
        wing = 7 + (pyxel.frame_count + seed) % 3
        pyxel.line(sx - wing, sy, sx, sy - 3, C7)
        pyxel.line(sx, sy - 3, sx + wing, sy, C7)
        pyxel.line(sx - wing + 2, sy + 2, sx, sy, C6)
        pyxel.line(sx, sy, sx + wing - 2, sy + 2, C6)
        pyxel.tri(sx + dx, sy + dy, sx - 2, sy - 2, sx - 2, sy + 2, C10)
        pyxel.pset(sx + dx, sy + dy, C7)
    elif style == 'royal_cannonball':
        pyxel.circ(sx, sy, 4, C5)
        pyxel.circb(sx, sy, 4, C10)
        pyxel.pset(sx - 2, sy - 5, C10); pyxel.pset(sx, sy - 6, C10); pyxel.pset(sx + 2, sy - 5, C10)
        pyxel.line(sx1, sy1, sx - dx // 2, sy - dy // 2, C6)
    elif style == 'laurel_spear':
        pyxel.line(sx - dx * 2, sy - dy * 2, sx + dx, sy + dy, C7)
        pyxel.tri(sx + dx, sy + dy, sx + dx - dy // 2, sy + dy + dx // 2, sx + dx + dy // 2, sy + dy - dx // 2, C8)
        for off in (-8, -4, 0):
            bx = sx - dx + off * (1 if dx == 0 else sign(dx))
            pyxel.pset(bx, sy - 3, C3); pyxel.pset(bx + 1, sy + 3, C3)
    elif style == 'legionary_pilum':
        pyxel.line(sx - dx * 3, sy - dy * 3, sx + dx, sy + dy, C4)
        pyxel.line(sx - dx * 2, sy - dy * 2, sx + dx, sy + dy, C7)
        pyxel.rect(sx - 2, sy - 2, 4, 4, C8)
        pyxel.tri(sx + dx, sy + dy, sx + dx - dy, sy + dy + dx, sx + dx + dy, sy + dy - dx, C7)
    elif style == 'wolf_spirit':
        pyxel.circ(sx, sy, 5, C7)
        pyxel.tri(sx - 4, sy - 3, sx - 1, sy - 8, sx + 1, sy - 3, C7)
        pyxel.tri(sx + 4, sy - 3, sx + 1, sy - 8, sx - 1, sy - 3, C7)
        pyxel.pset(sx - 2, sy, C12); pyxel.pset(sx + 2, sy, C12)
        pyxel.circb(sx - dx, sy - dy, 6, C12)
    elif style == 'gladiator_net':
        s = 7
        pyxel.rectb(sx - s, sy - s, s * 2, s * 2, C7)
        for k in range(-s, s + 1, 4):
            pyxel.line(sx - s, sy + k, sx + s, sy + k, C6)
            pyxel.line(sx + k, sy - s, sx + k, sy + s, C6)
        pyxel.pset(sx, sy, C10)
    elif style == 'samurai_star':
        spin = ((pyxel.frame_count + seed) // 3) % 2
        if spin:
            pyxel.line(sx - 6, sy, sx + 6, sy, C7); pyxel.line(sx, sy - 6, sx, sy + 6, C7)
            pyxel.line(sx - 4, sy - 4, sx + 4, sy + 4, C12); pyxel.line(sx - 4, sy + 4, sx + 4, sy - 4, C12)
        else:
            pyxel.line(sx - 5, sy - 5, sx + 5, sy + 5, C7); pyxel.line(sx - 5, sy + 5, sx + 5, sy - 5, C7)
            pyxel.line(sx - 6, sy, sx + 6, sy, C12); pyxel.line(sx, sy - 6, sx, sy + 6, C12)
        pyxel.pset(sx, sy, C8)
    elif style == 'baguette':
        pyxel.line(sx - dx * 2, sy - dy * 2, sx + dx, sy + dy, C10)
        pyxel.line(sx - dx, sy - dy - 1, sx + dx // 2, sy + dy // 2 - 1, C7)
        for i in range(3):
            px = sx - dx + i * dx // 2
            py = sy - dy + i * dy // 2
            pyxel.pset(px, py, C4)
    elif style == 'piranha_swarm':
        for i in range(5):
            ox = int(math.sin((pyxel.frame_count + seed + i * 11) * .35) * 7) - i * 2
            oy = int(math.cos((pyxel.frame_count + seed + i * 13) * .31) * 5)
            pyxel.tri(sx + ox - 3, sy + oy, sx + ox + 3, sy + oy - 2, sx + ox + 3, sy + oy + 2, C8 if i % 2 else C12)
            pyxel.pset(sx + ox + 2, sy + oy - 1, C7)
    elif style == 'scimitar':
        pyxel.circb(sx, sy, 7, C10)
        pyxel.circ(sx - 3, sy - 1, 7, C0)
        pyxel.line(sx - 4, sy + 4, sx - 8, sy + 7, C5)
        pyxel.pset(sx + 5, sy - 3, C7)
    elif style == 'bear_head':
        pyxel.circ(sx, sy, 5, C4)
        pyxel.circ(sx - 4, sy - 4, 2, C4); pyxel.circ(sx + 4, sy - 4, 2, C4)
        pyxel.rect(sx - 2, sy + 1, 5, 3, C7)
        pyxel.pset(sx - 2, sy - 1, C0); pyxel.pset(sx + 2, sy - 1, C0)
        pyxel.circb(sx, sy, 7, C8)
    elif style == 'thick_yellow_gas':
        for ox, oy, rr in [(-7, 0, 4), (-2, -3, 5), (4, 2, 5), (8, -2, 3), (1, 5, 3)]:
            wob = int(math.sin((pyxel.frame_count + seed + ox * 3) * .22) * 2)
            pyxel.circ(sx + ox + wob, sy + oy, rr, C10)
            pyxel.circb(sx + ox + wob, sy + oy, rr + 1, C7 if (pyxel.frame_count + ox) % 5 == 0 else C4)
        pyxel.line(sx1, sy1, sx, sy, C10)
    elif style == 'empire_flag_bolt':
        pyxel.line(sx1, sy1, sx, sy, self.color)
        pyxel.circ(sx, sy, self.radius + 2, C14)
        pyxel.pset(sx, sy, C7)
    else:
        return _old_bullet_draw_v15(self, cam_x)
Bullet.draw = _bullet_draw_v15
def _draw_dash_icon_v15(key, x, y, i, empire=False):
    if key == 'china':
        pyxel.circ(x, y, 3 + i % 2, C8); pyxel.pset(x + 2, y - 1, C10)
    elif key == 'usa':
        pyxel.line(x - 6, y, x, y - 3, C7); pyxel.line(x, y - 3, x + 6, y, C7); pyxel.pset(x, y, C10)
    elif key == 'uk':
        pyxel.circ(x, y, 3, C5); pyxel.pset(x, y - 4, C10)
    elif key == 'italy':
        pyxel.line(x - 5, y + 3, x + 5, y - 3, C7); pyxel.pset(x - 2, y - 3, C3); pyxel.pset(x + 2, y + 3, C3)
    elif key == 'japan':
        pyxel.line(x - 4, y, x + 4, y, C7); pyxel.line(x, y - 4, x, y + 4, C7); pyxel.pset(x, y, C8)
    elif key == 'france':
        pyxel.line(x - 5, y + 2, x + 5, y - 2, C10); pyxel.pset(x, y, C7)
    elif key == 'brazil':
        pyxel.tri(x - 4, y, x + 4, y - 3, x + 4, y + 3, C3); pyxel.pset(x + 2, y, C10)
    elif key == 'turkiye':
        pyxel.circb(x, y, 5, C10); pyxel.circ(x - 2, y, 5, C0); pyxel.pset(x + 4, y - 1, C7)
    elif key == 'russia':
        pyxel.circ(x, y, 4, C4); pyxel.circ(x - 3, y - 3, 2, C4); pyxel.circ(x + 3, y - 3, 2, C4)
    elif key == 'germany':
        pyxel.circ(x - 3, y, 3, C10); pyxel.circ(x + 2, y - 2, 4, C10); pyxel.circb(x, y, 6, C4)
    else:
        draw_flag(key, x - 6, y - 4, 12, 8, pyxel.frame_count * .2 + i)
    if empire:
        pyxel.circb(x, y, 7 + i, C14 if i % 2 else C10)
_old_player_draw_v15 = Player.draw
def _player_draw_v15(self, cam_x):
    game = globals().get('_active_secret_game', None)
    if getattr(self, 'dash_timer', 0) > 0:
        key = self.country.get('key', 'usa') if hasattr(self, 'country') else 'usa'
        if game is not None:
            try:
                key = _v13_country_key(game)
            except Exception:
                pass
        sx = int(self.x - cam_x)
        sy = int(self.y + 12)
        empire = bool(game is not None and getattr(game, 'empire_mode', False))
        for i in range(5 if empire else 3):
            tx = sx - self.facing * (12 + i * 11)
            ty = sy + int(math.sin((pyxel.frame_count + i * 5) * .5) * (3 + i % 2))
            _draw_dash_icon_v15(key, tx, ty, i, empire)
            if empire:
                pyxel.line(tx, ty, sx - self.facing * 4, sy, C14 if i % 2 else C10)
    return _old_player_draw_v15(self, cam_x)
Player.draw = _player_draw_v15
def _setup_tutorial_v15(game):
    game.tutorial_step = 0
    game.tutorial_flags = {"jump":False,"move":False,"weapon":False,"balloon":False,"rocket":False,"bomb":False,"dash":False,"pause":False}
    game.tutorial_target_intro = 0
    game.tutorial_balloon = {"x":214,"y":78,"alive":True}
    game.tutorial_drone = {"x":218,"y":70,"alive":True,"arrive":0}
    game.tutorial_turret = {"x":232,"alive":True,"rise":0}
    game._tutorial_done_timer = 75
    try:
        game.player.reset(58, game.difficulty_value())
        game.player.rockets = max(game.player.rockets, 3)
        game.player.bombs = max(game.player.bombs, 3)
        game.cam_x = 0
    except Exception:
        pass
globals()['_setup_tutorial_v13'] = _setup_tutorial_v15
_TUTORIAL_ORDER_V15 = ["jump", "move", "weapon", "balloon", "rocket", "bomb", "dash", "pause"]
def _current_tutorial_need_v15(game):
    if game.tutorial_step >= len(_TUTORIAL_ORDER_V15):
        return "done"
    return _TUTORIAL_ORDER_V15[game.tutorial_step]
def _update_tutorial_v15(self):
    if not hasattr(self, 'tutorial_flags') or 'weapon' not in getattr(self, 'tutorial_flags', {}):
        _setup_tutorial_v15(self)
    if pyxel.btnp(pyxel.KEY_RETURN):
        self.state = "playing"
        return
    need_before = _current_tutorial_need_v15(self)
    if pyxel.btnp(pyxel.KEY_P):
        self.tutorial_flags["pause"] = True
    if pyxel.btnp(pyxel.KEY_SHIFT):
        self.tutorial_flags["weapon"] = True
    old_x, old_y = self.player.x, self.player.y
    self.player.update(self.difficulty_value(), getattr(self, 'dev_mode', False))
    self.player.x = clamp(self.player.x, 8, 292)
    self.cam_x = 0
    if abs(self.player.x - old_x) > .4:
        self.tutorial_flags["move"] = True
    if pyxel.btnp(pyxel.KEY_W) or self.player.y < old_y - 1:
        self.tutorial_flags["jump"] = True
    if pyxel.btnp(pyxel.KEY_SPACE) or getattr(self.player, 'dash_timer', 0) > 0:
        self.tutorial_flags["dash"] = True
    self.handle_player_fire()
    for b in self.player_bullets:
        b.update()
    for r in self.rockets:
        r.update([])
    for b in self.bombs:
        b.update()
    for e in self.effects:
        e.update()
    need = _current_tutorial_need_v15(self)
    if need == 'balloon':
        bal = self.tutorial_balloon
        if bal.get('alive'):
            for b in self.player_bullets:
                if dist2(b.x, b.y, bal['x'], bal['y']) < 14 * 14:
                    bal['alive'] = False; b.alive = False; self.tutorial_flags['balloon'] = True
                    self.spawn_explosion(bal['x'], bal['y'], 12, 'muzzle')
                    self.add_text(bal['x'], bal['y'] - 18, "BALLOON POPPED", C14)
    elif need == 'rocket':
        dr = self.tutorial_drone
        dr['arrive'] = min(60, dr.get('arrive', 0) + 2)
        if dr.get('alive'):
            for r in self.rockets:
                if dist2(r.x, r.y, dr['x'], dr['y']) < 20 * 20:
                    dr['alive'] = False; r.alive = False; self.tutorial_flags['rocket'] = True
                    self.spawn_explosion(dr['x'], dr['y'], 18, 'normal')
                    self.add_text(dr['x'] - 12, dr['y'] - 20, "DRONE DOWN", C10)
    elif need == 'bomb':
        tu = self.tutorial_turret
        tu['rise'] = min(24, tu.get('rise', 0) + 2)
        tx = tu['x']; ty = self.terrain.ground_y_at(tx, self.difficulty_value()) - 10
        if tu.get('alive'):
            for bomb in self.bombs:
                if abs(bomb.x - tx) < 22 and abs(bomb.y - ty) < 30:
                    tu['alive'] = False; bomb.alive = False; self.tutorial_flags['bomb'] = True
                    self.spawn_explosion(tx, ty, 20, 'normal')
                    self.add_text(tx - 20, ty - 20, "TURRET DESTROYED", C10)
    self.player_bullets = [b for b in self.player_bullets if b.alive]
    self.rockets = [r for r in self.rockets if r.alive]
    self.bombs = [b for b in self.bombs if b.alive]
    self.effects = [e for e in self.effects if e.alive]
    old_step = self.tutorial_step
    while self.tutorial_step < len(_TUTORIAL_ORDER_V15) and self.tutorial_flags.get(_TUTORIAL_ORDER_V15[self.tutorial_step], False):
        self.tutorial_step += 1
    if self.tutorial_step != old_step or need_before != _current_tutorial_need_v15(self):
        self.tutorial_target_intro = 0
    else:
        self.tutorial_target_intro = min(60, getattr(self, 'tutorial_target_intro', 0) + 1)
    if self.tutorial_step >= len(_TUTORIAL_ORDER_V15):
        self.add_text(self.player.x + 10, 42, "TRAINING COMPLETE", C14)
        self._tutorial_done_timer -= 1
        if self._tutorial_done_timer <= 0:
            self.state = "playing"
Game.update_tutorial = _update_tutorial_v15
def _tutorial_instruction_v15(game):
    steps = [
        "1) PRESS W TO JUMP",
        "2) HOLD A OR D TO MOVE",
        "3) PRESS SHIFT TO CHANGE WEAPON",
        "4) ARROWS: POP THE BALLOON",
        "5) K FOR ROCKET: HIT THE DRONE",
        "6) J FOR BOMB: DESTROY THE TURRET",
        "7) SPACE TO DASH",
        "8) P IS PAUSE",
        "TRAINING COMPLETE",
    ]
    return steps[min(getattr(game, 'tutorial_step', 0), len(steps) - 1)]
globals()['_tutorial_instruction_v13'] = _tutorial_instruction_v15
def _draw_training_map_v15(game):
    pyxel.cls(C0)
    for i in range(0, SCREEN_W, 18):
        h = 18 + (i * 7) % 34
        pyxel.rect(i, 132 - h, 10, h, C5)
        if i % 36 == 0:
            pyxel.pset(i + 5, 130 - h, C8)
    game.terrain.draw(0, game.difficulty_value())
    pyxel.text(8, 6, _tutorial_instruction_v15(game), C7)
    pyxel.text(8, 16, "ENTER SKIP", C6)
    pyxel.text(8, 28, "ALL CONTROLS ARE ACTIVE", C12)
    labels = [("W JUMP", "jump"), ("A/D MOVE", "move"), ("SHIFT WEAPON", "weapon"), ("ARROWS BALLOON", "balloon"), ("K ROCKET DRONE", "rocket"), ("J BOMB TURRET", "bomb"), ("SPACE DASH", "dash"), ("P PAUSE", "pause")]
    y = 42
    for idx, (label, flag) in enumerate(labels):
        col = C10 if game.tutorial_flags.get(flag) else (C14 if idx == game.tutorial_step else C5)
        pyxel.text(8, y, ("OK " if game.tutorial_flags.get(flag) else "-- ") + label, col)
        y += 8
    need = _current_tutorial_need_v15(game)
    intro = clamp(getattr(game, 'tutorial_target_intro', 0) / 28, 0, 1)
    if need == 'balloon' and game.tutorial_balloon.get('alive'):
        bal = game.tutorial_balloon
        bx = int(bal['x'])
        by = int(-24 + (bal['y'] + 24) * intro)
        pyxel.circ(bx, by, 8, C8); pyxel.circ(bx - 2, by - 2, 3, C14); pyxel.line(bx, by + 8, bx, by + 22, C7)
        pyxel.text(bx - 31, by - 20, "POP BALLOON", C14)
        pyxel.line(150, 30, bx - 8, by, C6)
    elif need == 'rocket' and game.tutorial_drone.get('alive'):
        dr = game.tutorial_drone
        dx = int(SCREEN_W + 30 - (SCREEN_W + 30 - dr['x']) * intro)
        dy = dr['y']
        pyxel.rect(dx - 12, dy - 5, 24, 10, C5); pyxel.rectb(dx - 12, dy - 5, 24, 10, C12)
        pyxel.circ(dx - 16, dy, 3, C7); pyxel.circ(dx + 16, dy, 3, C7)
        pyxel.text(170, 48, "K = ROCKET", C10)
    elif need == 'bomb' and game.tutorial_turret.get('alive'):
        tu = game.tutorial_turret
        tx = tu['x']; ground = game.terrain.ground_y_at(tx, game.difficulty_value())
        rise = int(24 * intro)
        ty = ground - 10 + (24 - rise)
        pyxel.rect(tx - 8, ty, 16, 10, C4); pyxel.rectb(tx - 8, ty, 16, 10, C8); pyxel.line(tx, ty, tx + 12, ty - 8, C7)
        pyxel.text(178, 126, "J = BOMB TURRET", C8)
    elif need == 'weapon':
        pyxel.rect(184, 58, 78, 24, C0); pyxel.rectb(184, 58, 78, 24, C14)
        pyxel.text(190, 64, "SHIFT CHANGES", C14)
        pyxel.text(196, 73, "BLASTER/SPREAD", C7)
    for obj_list in (game.player_bullets, game.rockets, game.bombs, game.effects, game.particles, game.texts):
        for o in obj_list:
            if hasattr(o, 'draw'):
                o.draw(0)
    game.player.draw(0)
globals()['_draw_training_map_v13'] = _draw_training_map_v15
def _draw_tutorial_v15(self):
    if not hasattr(self, 'tutorial_flags') or 'weapon' not in getattr(self, 'tutorial_flags', {}):
        _setup_tutorial_v15(self)
    _draw_training_map_v15(self)
Game.draw_tutorial = _draw_tutorial_v15
_old_boss_init_v15 = BossEnemy.__init__
def _boss_init_v15(self, x):
    _old_boss_init_v15(self, x)
    self.super_laser_cd = random.randint(FPS * 3, FPS * 5)
    self.super_laser_timer = 0
    self.super_laser_target_y = 100
    self.super_laser_hit = False
BossEnemy.__init__ = _boss_init_v15
_old_boss_update_v15 = BossEnemy.update
def _boss_update_v15(self, game):
    _old_boss_update_v15(self, game)
    if not getattr(self, 'alive', True):
        return
    if getattr(self, 'super_laser_timer', 0) > 0:
        self.super_laser_timer -= 1
        if self.super_laser_timer > 34:
            self.super_laser_target_y = int(game.player.y + PLAYER_H / 2)
            if self.super_laser_timer % 10 == 0:
                game.particles.append(Particle(self.x + 38, self.y + 31, random.uniform(-1.2, 1.2), random.uniform(-1.2, 1.2), random.choice([C8, C10, C14, C7]), 20, size=2, gravity=0))
        else:
            beam_y = self.super_laser_target_y
            px, py, pw, ph = game.player.hurtbox()
            player_cy = py + ph / 2
            if (not getattr(self, 'super_laser_hit', False)) and abs(player_cy - beam_y) < 13 and px < self.x + self.w + 80:
                game.player_hit(1)
                self.super_laser_hit = True
                game.add_shake(18, 5)
        return
    self.super_laser_cd -= 1
    if self.super_laser_cd <= 0 and self.hp < self.max_hp * 0.92:
        self.super_laser_timer = FPS + 34
        self.super_laser_cd = random.randint(FPS * 4, FPS * 7)
        self.super_laser_target_y = int(game.player.y + PLAYER_H / 2)
        self.super_laser_hit = False
        game.add_shake(20, 3)
        sfx(0, 7)
BossEnemy.update = _boss_update_v15
_old_boss_draw_v15 = BossEnemy.draw
def _boss_draw_v15(self, cam_x):
    _old_boss_draw_v15(self, cam_x)
    timer = getattr(self, 'super_laser_timer', 0)
    if timer <= 0:
        return
    sx = int(self.x - cam_x)
    sy = int(self.y)
    core_x = sx + 38
    core_y = sy + 31
    beam_y = int(getattr(self, 'super_laser_target_y', core_y))
    if timer > 34:
        pyxel.circb(core_x, core_y, 16 + (pyxel.frame_count // 3) % 8, C10)
        pyxel.circb(core_x, core_y, 25 + (pyxel.frame_count // 4) % 9, C8)
        pyxel.line(0, beam_y, SCREEN_W, beam_y, C8 if (pyxel.frame_count // 4) % 2 else C10)
        pyxel.rect(72, 24, 176, 26, C0)
        pyxel.rectb(72, 24, 176, 26, C8)
        pyxel.text(91, 31, "COMIC CUT-IN: SUPER LASER", C10)
        pyxel.text(99, 40, "JUMP OR DROP OUT OF THE LINE", C7)
    else:
        for off, col in [(-7, C8), (-4, C10), (0, C7), (4, C10), (7, C8)]:
            pyxel.line(core_x, core_y + off // 2, 0, beam_y + off, col)
            pyxel.line(core_x, core_y + off // 2, SCREEN_W, beam_y + off, col)
        pyxel.circ(core_x, core_y, 18, C7)
        pyxel.circb(core_x, core_y, 26, C10)
        if (pyxel.frame_count // 2) % 2 == 0:
            pyxel.text(119, 26, "KRA-KOOM", C8)
BossEnemy.draw = _boss_draw_v15
_old_enemy_hurt_v15 = Enemy.hurt
def _enemy_hurt_v15(self, dmg):
    was_boss = isinstance(self, BossEnemy)
    old_hp = getattr(self, 'hp', 0)
    dead = _old_enemy_hurt_v15(self, dmg)
    if was_boss and old_hp > getattr(self, 'hp', 0):
        game = globals().get('_active_secret_game', None)
        if game is not None:
            hx = self.x + random.randint(8, max(9, self.w - 8))
            hy = self.y + random.randint(8, max(9, self.h - 8))
            game.effects.append(Explosion(hx, hy, random.randint(8, 15), 'normal'))
            for _p in range(8):
                game.particles.append(Particle(hx, hy, random.uniform(-2.4, 2.4), random.uniform(-2.2, 1.4), random.choice([C8, C9, C10, C7]), random.randint(14, 38), size=random.choice([1, 1, 2]), gravity=.05))
    return dead
Enemy.hurt = _enemy_hurt_v15
_old_kill_enemy_v15 = Game.kill_enemy
def _kill_enemy_v15(self, enemy):
    was_boss = isinstance(enemy, BossEnemy)
    bx = getattr(enemy, 'x', self.player.x)
    by = getattr(enemy, 'y', 80)
    result = _old_kill_enemy_v15(self, enemy)
    if was_boss:
        self.boss_killed_this_level = True
        self.level_boss_spawned = True
        self.level_exit_ready = False
        self.boss_death_cinematic = max(getattr(self, 'boss_death_cinematic', 0), FPS * 4)
        self.boss_death_total = FPS * 4
        self.boss_death_x = bx
        self.boss_death_y = by
        self.boss_death_v15 = True
        self.add_shake(FPS * 2, 8)
        for _ in range(12):
            self.effects.append(Explosion(bx + random.randint(0, 76), by + random.randint(0, 74), random.randint(16, 36), 'normal'))
        for _p in range(92):
            self.particles.append(Particle(bx + 38 + random.uniform(-42, 42), by + 34 + random.uniform(-34, 34), random.uniform(-4.5, 4.5), random.uniform(-4.0, 2.4), random.choice([C8, C9, C10, C7, C5]), random.randint(30, 90), size=random.choice([1, 1, 2, 3]), gravity=.045))
    return result
Game.kill_enemy = _kill_enemy_v15
_old_draw_world_v15 = Game.draw_world
def _draw_world_v15(self):
    _old_draw_world_v15(self)
    if getattr(self, 'boss_death_cinematic', 0) > 0:
        t = getattr(self, 'boss_death_cinematic', 0)
        total = max(FPS * 4, getattr(self, 'boss_death_total', FPS * 4))
        elapsed = total - t
        cx = int(getattr(self, 'boss_death_x', self.player.x) - self.cam_x + 38)
        cy = int(getattr(self, 'boss_death_y', 72) + 34)
        pyxel.camera(0, 0)
        if elapsed < 55:
            pyxel.rect(18, 16, 126, 42, C0); pyxel.rectb(18, 16, 126, 42, C7)
            pyxel.text(29, 28, "CORE CRACKS", C10)
            pyxel.text(36, 40, "!!!", C8)
        elif elapsed < 125:
            pyxel.rect(174, 14, 128, 48, C0); pyxel.rectb(174, 14, 128, 48, C8)
            pyxel.text(188, 27, "MISSILES DETONATE", C10)
            pyxel.text(204, 41, "BOOM BOOM", C8)
        else:
            pyxel.rect(61, 138, 204, 26, C0); pyxel.rectb(61, 138, 204, 26, C14)
            pyxel.text(83, 147, "BOSS DESTROYED - EXIT UNLOCKING", C14)
        r = 24 + elapsed // 2
        pyxel.circb(cx, cy, min(110, r), C8)
        pyxel.circb(cx, cy, min(124, r + 14), C10)
        pyxel.circb(cx, cy, min(140, r + 28), C7 if (pyxel.frame_count // 3) % 2 else C14)
Game.draw_world = _draw_world_v15
_old_next_level_v15 = Game.next_level
def _next_level_v15(self):
    self.boss_death_cinematic = 0
    self.boss_death_total = 0
    self.boss_death_v15 = False
    self.earthquake_timer = 0
    result = _old_next_level_v15(self)
    self.boss_death_cinematic = 0
    self.boss_death_total = 0
    self.boss_death_v15 = False
    self.earthquake_timer = 0
    if getattr(self, 'empire_mode', False):
        self.player.max_hp = 1
        self.player.hp = 1
    return result
Game.next_level = _next_level_v15
_old_reset_run_v15 = Game.reset_run
def _reset_run_v15(self):
    result = _old_reset_run_v15(self)
    self.boss_death_cinematic = 0
    self.boss_death_total = 0
    self.boss_death_v15 = False
    self.earthquake_timer = 0
    if getattr(self, 'empire_mode', False):
        self.player.max_hp = 1
        self.player.hp = 1
    return result
Game.reset_run = _reset_run_v15
_old_draw_story_update_v15 = Game.draw_story_update
def _draw_story_update_v15(self):
    if getattr(self, 'empire_mode', False) and not getattr(self, 'empire_won', False):
        pyxel.cls(C0)
        key = self.current_country()["key"]
        pyxel.text(76, 22, "ASCENSION PROTOCOL", C14)
        pyxel.text(52, 38, self.current_country()["name"] + " HAS BEEN UPGRADED INTO", C7)
        pyxel.text(70, 52, _empire_name(self), C10)
        draw_country_outline(key, 108, 66, scale=3, color=C14, fill=False)
        draw_flag(key, 146, 94, 30, 18, pyxel.frame_count * 0.25)
        pyxel.text(48, 128, "FOURTEEN MORE LEVELS. ONE LIFE.", C8)
        pyxel.text(50, 142, "CUSTOM EMPIRE WEAPONS ARE ONLINE.", C7)
        pyxel.text(90, 170, "PRESS ENTER TO CONTINUE", C10)
        return
    _old_draw_story_update_v15(self)
Game.draw_story_update = _draw_story_update_v15
_old_draw_empire_ascension_v15 = getattr(Game, 'draw_empire_ascension', None)
def _draw_empire_ascension_v15(self):
    if _old_draw_empire_ascension_v15:
        _old_draw_empire_ascension_v15(self)
    pyxel.camera(0, 0)
    pyxel.rect(62, 156, 198, 15, C0)
    pyxel.rectb(62, 156, 198, 15, C14)
    pyxel.text(74, 161, "UPGRADE: 14 ONE-LIFE EMPIRE LEVELS", C10)
Game.draw_empire_ascension = _draw_empire_ascension_v15
def _draw_bunkers_v15(self):
    if not _campaign_like_v14(self):
        try:
            if '_draw_secret_dev_bunker' in globals():
                _draw_secret_dev_bunker(self)
        except Exception:
            pass
        return
    try:
        _ensure_exit_gate_v13(self)
    except Exception:
        pass
    level_len = _level_len_v14(self)
    start_x = getattr(self, 'checkpoint_x', 40) - 28
    gy = self.terrain.ground_y_at(start_x + 18, self.difficulty_value())
    if -80 <= start_x - self.cam_x <= SCREEN_W + 80:
        self.draw_bunker_style(start_x, gy, self.current_country()['key'])
    exit_x = getattr(self, 'level_exit_x', getattr(self, 'checkpoint_x', 40) + level_len)
    ready = bool(getattr(self, 'level_exit_ready', False) or getattr(self, 'distance', 0) > level_len - 620 or getattr(self, 'boss_killed_this_level', False))
    if ready:
        gy2 = self.terrain.ground_y_at(exit_x + 18, self.difficulty_value())
        sx2 = int(exit_x - self.cam_x)
        if -100 <= sx2 <= SCREEN_W + 120:
            self.draw_bunker_style(exit_x, gy2, self.current_country()['key'])
            if abs((self.player.x + PLAYER_W / 2) - (exit_x + 18)) < 82:
                pyxel.rect(sx2 - 8, gy2 - 58, 58, 17, C0)
                pyxel.rectb(sx2 - 8, gy2 - 58, 58, 17, C14)
                pyxel.text(sx2 - 4, gy2 - 54, 'NEXT LVL', C14)
                if (pyxel.frame_count // 12) % 2 == 0:
                    pyxel.text(sx2 + 4, gy2 - 46, 'ENTER', C10)
    try:
        if '_draw_secret_dev_bunker' in globals():
            _draw_secret_dev_bunker(self)
    except Exception:
        pass
Game.draw_bunkers = _draw_bunkers_v15
FINAL_BUILD_TAG = "v15-earthquake-empire-boss-polish"
def _v16_boss_active(game):
    try:
        if getattr(game, 'boss_death_cinematic', 0) > 0:
            return True
        return any(isinstance(e, BossEnemy) and getattr(e, 'alive', True) for e in getattr(game, 'enemies', []))
    except Exception:
        return False
def _v16_country_key(game_or_player):
    try:
        return _v13_country_key(game_or_player)
    except Exception:
        try:
            if hasattr(game_or_player, 'current_country'):
                return game_or_player.current_country().get('key', 'usa')
            return game_or_player.country.get('key', 'usa')
        except Exception:
            return 'usa'
_old_ensure_v14_state_v16 = globals().get('_ensure_v14_state')
def _ensure_v14_state_v16(game):
    if _old_ensure_v14_state_v16:
        _old_ensure_v14_state_v16(game)
    if not hasattr(game, 'earthquake_warning_timer'):
        game.earthquake_warning_timer = 0
    if not hasattr(game, 'earthquake_missile_cd'):
        game.earthquake_missile_cd = 0
    if not hasattr(game, 'earthquake_fast_phase'):
        game.earthquake_fast_phase = 0
    if not hasattr(game, 'quake_red_border_timer'):
        game.quake_red_border_timer = 0
    if not hasattr(game, 'landmine_spawn_cd'):
        game.landmine_spawn_cd = FPS * 3
globals()['_ensure_v14_state'] = _ensure_v14_state_v16
def _missile_spawn_chance_v16(self):
    try:
        base = _old_missile_chance_v14(self)
    except Exception:
        base = min(0.006 + self.danger_scale() * 0.005, 0.12)
    mult = 0.94
    if getattr(self, 'earthquake_timer', 0) > 0:
        mult = 1.15
    return min(base * mult, 0.145)
def _drone_spawn_chance_v16(self):
    try:
        base = _old_drone_chance_v14(self)
    except Exception:
        base = min(0.002 + self.danger_scale() * 0.0026, 0.04)
    mult = 0.90
    if getattr(self, 'earthquake_timer', 0) > 0:
        mult = 1.02
    return min(base * mult, 0.052)
def _turret_spawn_chance_v16(self):
    try:
        base = _old_turret_chance_v14(self)
    except Exception:
        d = self.difficulty_value()
        base = 0 if d < 2.2 else min(0.001 + d * 0.0014, 0.012)
    return min(base * 0.88, 0.015)
def _bomber_spawn_chance_v16(self):
    try:
        base = _old_bomber_chance_v14(self)
    except Exception:
        d = self.difficulty_value()
        base = 0 if d < 4.8 else min(0.00035 + d * 0.00022, 0.0024)
    d = self.difficulty_value()
    if d < 2.25:
        return 0
    return min(max(base * 3.15, 0.0008 + d * 0.00014), 0.0072)
Game.missile_spawn_chance = _missile_spawn_chance_v16
Game.drone_spawn_chance = _drone_spawn_chance_v16
Game.turret_spawn_chance = _turret_spawn_chance_v16
Game.bomber_spawn_chance = _bomber_spawn_chance_v16
def _maybe_spawn_landmine_v16(game):
    _ensure_v14_state(game)
    if getattr(game, 'boss_death_cinematic', 0) > 0 or _v16_boss_active(game):
        return
    if getattr(game, 'state', '') != 'playing':
        return
    game.landmine_spawn_cd -= 1
    if game.landmine_spawn_cd > 0:
        return
    game.landmine_spawn_cd = random.randint(FPS * 3, FPS * 6)
    if game.difficulty_value() < 1.45 or random.random() > 0.72:
        return
    for _ in range(12):
        tx = game.player.x + random.randint(130, 420)
        if abs(tx - game.player.x) < 110:
            continue
        if game.terrain.is_gap_at(tx + 8, game.difficulty_value()):
            continue
        too_close = False
        for e in getattr(game, 'enemies', []):
            if isinstance(e, LandmineObstacle) and abs(e.x - tx) < 90:
                too_close = True
                break
        if too_close:
            continue
        gy = game.terrain.ground_y_at(tx + 8, game.difficulty_value())
        game.enemies.append(LandmineObstacle(tx, gy))
        if random.random() < 0.22 and game.difficulty_value() > 5.2:
            tx2 = tx + random.randint(86, 145)
            if not game.terrain.is_gap_at(tx2 + 8, game.difficulty_value()):
                gy2 = game.terrain.ground_y_at(tx2 + 8, game.difficulty_value())
                game.enemies.append(LandmineObstacle(tx2, gy2))
        return
globals()['_maybe_spawn_landmine_v14'] = _maybe_spawn_landmine_v16
def _terrain_add_jumpable_trench_v16(terrain, x, radius=None):
    if radius is None:
        radius = random.randint(18, 28)
    terrain.craters.append({
        'x': x,
        'r': radius,
        'gap_r': random.randint(7, 13),
        'depth': random.randint(20, 34),
    })
    if len(terrain.craters) > 58:
        terrain.craters.pop(0)
def _terrain_add_uplift_v16(terrain, x):
    if not hasattr(terrain, 'uplifts'):
        terrain.uplifts = []
    terrain.uplifts.append({
        'x': x,
        'r': random.randint(28, 58),
        'height': random.randint(12, 28),
    })
    if len(terrain.uplifts) > 38:
        terrain.uplifts.pop(0)
def _begin_earthquake_warning_v16(game):
    _ensure_v14_state(game)
    if getattr(game, 'earthquake_warning_timer', 0) > 0 or getattr(game, 'earthquake_timer', 0) > 0:
        return
    game.earthquake_warning_timer = FPS * 2
    game.quake_red_border_timer = FPS * 2
    game.add_text(game.player.x + 32, 38, 'EARTHQUAKE WARNING', C8)
    game.add_shake(24, 5)
    sfx(0, 6)
def _start_earthquake_v16(game):
    _ensure_v14_state(game)
    if game.earthquake_timer > 0:
        return
    game.earthquake_timer = random.randint(FPS * 5, FPS * 7)
    game.earthquake_trench_cd = 1
    game.earthquake_missile_cd = random.randint(10, 24)
    game.earthquake_recent_x = []
    game.earthquake_fast_phase = 0
    game.quake_red_border_timer = max(getattr(game, 'quake_red_border_timer', 0), game.earthquake_timer)
    game.earthquake_next_level = int(getattr(game, 'level', 1)) + random.choice([1, 2])
    game.add_text(game.player.x + 28, 42, 'EARTHQUAKE - JUMP!', C14)
    game.add_shake(game.earthquake_timer, 14)
    sfx(0, 7)
globals()['_start_earthquake_v14'] = _start_earthquake_v16
def _pick_quake_x_v16(game):
    recent = getattr(game, 'earthquake_recent_x', [])
    for _ in range(24):
        tx = game.player.x + random.choice([
            random.randint(62, 155), random.randint(165, 300),
            random.randint(310, 520), random.randint(-70, -42)
        ])
        if abs(tx - game.player.x) < 48:
            continue
        blocked = False
        for c in getattr(game.terrain, 'craters', []):
            if abs(tx - c.get('x', 0)) < c.get('r', 0) + 54:
                blocked = True
                break
        if blocked:
            continue
        for rx in recent[-12:]:
            if abs(tx - rx) < 70:
                blocked = True
                break
        if not blocked:
            return tx
    return game.player.x + random.randint(110, 430)
def _spawn_quake_missile_v16(game):
    try:
        x = game.cam_x + random.randint(10, SCREEN_W - 18)
        sp = 1.55 + game.danger_scale() * 0.34 + random.random() * 0.55
        game.enemies.append(MissileEnemy(x, sp))
    except Exception:
        pass
def _update_earthquake_v16(game):
    _ensure_v14_state(game)
    if not _campaign_like_v14(game) or getattr(game, 'state', '') != 'playing':
        return
    level_len = _level_len_v14(game)
    if getattr(game, 'earthquake_warning_timer', 0) > 0:
        game.earthquake_warning_timer -= 1
        game.quake_red_border_timer = max(getattr(game, 'quake_red_border_timer', 0), game.earthquake_warning_timer)
        if pyxel.frame_count % 3 == 0:
            game.shake_x = random.choice([-5, -3, 3, 5])
            game.shake_y = random.choice([-3, 0, 3])
        if game.earthquake_warning_timer <= 0:
            _start_earthquake_v16(game)
        return
    if game.earthquake_timer <= 0:
        if int(getattr(game, 'level', 1)) >= int(getattr(game, 'earthquake_next_level', 2)) and 0.18 < game.distance / max(1, level_len) < 0.82:
            _begin_earthquake_warning_v16(game)
        return
    game.earthquake_timer -= 1
    game.quake_red_border_timer = max(getattr(game, 'quake_red_border_timer', 0), game.earthquake_timer)
    game.earthquake_fast_phase = getattr(game, 'earthquake_fast_phase', 0) + 1
    shake_choices = [-19, -16, -13, 13, 16, 19]
    if game.earthquake_fast_phase % 2 == 0:
        game.shake_x = random.choice(shake_choices)
        game.shake_y = random.choice([-11, -8, -5, 5, 8, 11])
    else:
        game.shake_x = random.randint(-16, 16)
        game.shake_y = random.randint(-9, 9)
    game.shake_timer = max(getattr(game, 'shake_timer', 0), 2)
    game.shake_power = max(getattr(game, 'shake_power', 0), 14)
    game.earthquake_missile_cd -= 1
    if game.earthquake_missile_cd <= 0:
        _spawn_quake_missile_v16(game)
        game.earthquake_missile_cd = random.randint(14, 31)
    game.earthquake_trench_cd -= 1
    if game.earthquake_trench_cd <= 0:
        game.earthquake_trench_cd = random.randint(8, 16)
        tx = _pick_quake_x_v16(game)
        game.earthquake_recent_x = (getattr(game, 'earthquake_recent_x', []) + [tx])[-14:]
        gy = game.terrain.base_ground_y_at(tx, game.difficulty_value())
        if random.random() < 0.56:
            _terrain_add_jumpable_trench_v16(game.terrain, tx)
            game.spawn_explosion(tx, gy, random.randint(11, 19), 'normal')
            game.texts.append(FlashText(tx - random.randint(4, 12), gy - random.randint(24, 40), '!', C8, life=46))
            if random.random() < 0.55:
                game.texts.append(FlashText(tx + random.randint(8, 22), gy - random.randint(18, 34), 'CRACK!', C14, life=36))
            for _p in range(18):
                game.particles.append(Particle(tx, gy, random.uniform(-3.0, 3.0), random.uniform(-3.8, -0.2), random.choice([C4, C5, C6, C7, C10]), random.randint(18, 48), size=random.choice([1, 1, 2]), gravity=.11))
        else:
            _terrain_add_uplift_v16(game.terrain, tx)
            game.spawn_explosion(tx, gy - 9, random.randint(8, 13), 'muzzle')
            game.texts.append(FlashText(tx, gy - random.randint(34, 48), '!', C10, life=38))
            for _p in range(16):
                game.particles.append(Particle(tx, gy - 6, random.uniform(-2.1, 2.1), random.uniform(-3.1, -0.4), random.choice([C3, C4, C5, C10]), random.randint(16, 38), size=random.choice([1, 1, 2]), gravity=.10))
        if random.random() < 0.34:
            ux = _pick_quake_x_v16(game)
            _terrain_add_uplift_v16(game.terrain, ux)
globals()['_update_earthquake_v14'] = _update_earthquake_v16
def _draw_red_danger_border_v16(label, color=C8):
    pulse = (pyxel.frame_count // 3) % 2 == 0
    pyxel.rectb(1, 25, SCREEN_W - 2, SCREEN_H - 27, color)
    if pulse:
        pyxel.rectb(3, 27, SCREEN_W - 6, SCREEN_H - 31, color)
    if label:
        pyxel.rect(97, 25, 126, 12, C0)
        pyxel.rectb(97, 25, 126, 12, color)
        pyxel.text(103, 29, label[:26], color)
def _draw_earthquake_overlay_v16(game):
    _ensure_v14_state(game)
    warning = getattr(game, 'earthquake_warning_timer', 0)
    active = getattr(game, 'earthquake_timer', 0)
    if warning <= 0 and active <= 0:
        return
    pyxel.camera(0, 0)
    if warning > 0:
        sec = max(1, int(math.ceil(warning / FPS)))
        _draw_red_danger_border_v16('QUAKE WARNING ' + str(sec), C8)
        if (pyxel.frame_count // 6) % 2 == 0:
            pyxel.text(113, 43, 'GROUND WILL BREAK', C14)
    else:
        _draw_red_danger_border_v16('EARTHQUAKE - JUMP!', C8)
        if (pyxel.frame_count // 4) % 2 == 0:
            pyxel.text(117, 43, '! TRENCHES !', C14)
globals()['_draw_earthquake_overlay_v14'] = _draw_earthquake_overlay_v16
_old_draw_world_v16_border = Game.draw_world
def _draw_world_v16_border(self):
    _old_draw_world_v16_border(self)
    try:
        if getattr(self.player, 'hp', 99) <= 1 and getattr(self, 'state', '') == 'playing':
            pyxel.camera(0, 0)
            _draw_red_danger_border_v16('1 HP - DANGER', C8)
    except Exception:
        pass
Game.draw_world = _draw_world_v16_border
_old_player_update_v16_weapon = Player.update
def _player_update_v16_weapon(self, difficulty, dev_mode=False):
    result = _old_player_update_v16_weapon(self, difficulty, dev_mode)
    game = globals().get('_active_secret_game', None)
    try:
        if not _v16_boss_active(game) and self.weapon_index < len(self.WEAPONS) and self.WEAPONS[self.weapon_index] == 'LASER':
            self.weapon_index = 0
    except Exception:
        pass
    return result
Player.update = _player_update_v16_weapon
_old_player_fire_v16_balance = Player.fire
def _player_fire_v16_balance(self):
    game = globals().get('_active_secret_game', None)
    try:
        if not _v16_boss_active(game) and self.weapon_index < len(self.WEAPONS) and self.WEAPONS[self.weapon_index] == 'LASER':
            self.weapon_index = 0
    except Exception:
        pass
    fired_weapon = self.weapon
    bullets, effects = _old_player_fire_v16_balance(self)
    if bullets:
        if fired_weapon == 'BLASTER':
            self.shot_cd = max(3, int(getattr(self, 'shot_cd', 8)) - 3)
        elif fired_weapon == 'SPREAD':
            self.shot_cd = int(getattr(self, 'shot_cd', 10)) + 8
        elif fired_weapon == 'LASER':
            if _v16_boss_active(game):
                self.shot_cd = max(8, int(getattr(self, 'shot_cd', 8)) + 2)
            else:
                for b in bullets:
                    b.alive = False
                bullets = []
    return bullets, effects
Player.fire = _player_fire_v16_balance
_old_update_field_order_v16 = globals().get('_update_field_order')
def _update_field_order_v16(game):
    if _v16_boss_active(game):
        try:
            game.field_order = None
            game.field_order_progress = 0
            game.field_order_timer = 0
            game.field_order_cooldown = max(getattr(game, 'field_order_cooldown', 0), FPS * 8)
        except Exception:
            pass
        return
    if _old_update_field_order_v16:
        return _old_update_field_order_v16(game)
globals()['_update_field_order'] = _update_field_order_v16
_old_draw_v12_radio_mission_v16 = globals().get('_draw_v12_radio_mission')
def _draw_v12_radio_mission_v16(game):
    if _v16_boss_active(game):
        return
    if _old_draw_v12_radio_mission_v16:
        return _old_draw_v12_radio_mission_v16(game)
globals()['_draw_v12_radio_mission'] = _draw_v12_radio_mission_v16
def _draw_empire_ascension_v16(self):
    pyxel.cls(C0)
    _ensure_empire_state(self)
    t = getattr(self, 'empire_anim_timer', getattr(self, 'empire_ascension_timer', getattr(self, 'intro_timer', 0)))
    key = self.current_country()["key"]
    name = REAL_EMPIRE_NAMES.get(key, self.current_country()["name"] + " EMPIRE") if 'REAL_EMPIRE_NAMES' in globals() else self.current_country()["name"] + " EMPIRE"
    for yy in range(0, SCREEN_H, 5):
        pyxel.line(0, yy, SCREEN_W, yy, C1 if (yy * 2 + t) % 34 < 15 else C0)
    pyxel.rect(8, 8, 304, 184, C0)
    pyxel.rectb(8, 8, 304, 184, C10 if (t // 10) % 2 else C14)
    pyxel.text(70, 18, "EMPIRE ASCENSION ONLINE", C14)
    pyxel.text(76, 31, name[:28], C10)
    p = clamp(t / 520.0, 0, 1)
    labels = [("TERRITORY", C3, p), ("MILITARY", C8, min(1, p * 1.12)), ("WEAPON CORE", C14, min(1, .40 + p * .60))]
    for i, (lab, col, val) in enumerate(labels):
        yy = 54 + i * 22
        pyxel.text(24, yy, lab, C7)
        pyxel.rect(92, yy, 106, 7, C1)
        pyxel.rectb(92, yy, 106, 7, C7)
        pyxel.rect(93, yy + 1, int(104 * val), 5, col)
    cx, cy = 160, 125
    pyxel.rect(cx - 31, cy - 50, 62, 78, C1)
    pyxel.rectb(cx - 31, cy - 50, 62, 78, C10)
    for i in range(7):
        pyxel.line(cx - 28, cy - 43 + i * 12, cx + 28, cy - 43 + i * 12 + int(math.sin((t + i * 11) / 10) * 5), C5)
    try:
        _draw_skin_preview(cx - 8, cy - 4, self.skin_index, key, empire=t > 150)
    except Exception:
        self.player.draw(self.cam_x)
    try:
        _v13_draw_banner(key, 222, 58, 46, 27, t * .2)
    except Exception:
        draw_flag(key, 222, 58, 46, 27, t * .2)
    pyxel.text(208, 95, "CUSTOM BULLETS", C10)
    pyxel.text(204, 109, "DASH TRAIL ONLINE", C14)
    pyxel.text(56, 157, "STATS: SPEED +0.10   JUMP +0.14   HP=1", C10)
    pyxel.text(66, 169, "EMPIRE WEAPONS ARE NOW ACTIVE", C7)
    if t > 390:
        pyxel.text(84, 184, "PRESS ENTER TO DEPLOY", C14)
Game.draw_empire_ascension = _draw_empire_ascension_v16
_old_draw_story_update_v16 = Game.draw_story_update
def _draw_story_update_v16(self):
    if getattr(self, 'empire_mode', False) and not getattr(self, 'empire_won', False):
        pyxel.cls(C0)
        key = self.current_country()["key"]
        pyxel.text(82, 22, "ASCENSION PROTOCOL", C14)
        pyxel.text(52, 38, self.current_country()["name"] + " HAS BEEN UPGRADED INTO", C7)
        pyxel.text(70, 52, _empire_name(self), C10)
        draw_country_outline(key, 108, 66, scale=3, color=C14, fill=False)
        draw_flag(key, 146, 94, 30, 18, pyxel.frame_count * 0.25)
        pyxel.text(59, 128, "CUSTOM EMPIRE WEAPONS ARE ONLINE.", C10)
        pyxel.text(61, 142, "HP=1. MOVE CLEAN. FIGHT SMART.", C7)
        pyxel.text(90, 170, "PRESS ENTER TO CONTINUE", C10)
        return
    return _old_draw_story_update_v16(self)
Game.draw_story_update = _draw_story_update_v16
def _tutorial_instruction_v16(game):
    steps = [
        "1) PRESS W TO JUMP",
        "2) HOLD A OR D TO MOVE",
        "3) SHIFT CHANGES GUN: READ THE BOX",
        "4) ARROWS: BLASTER POPS BALLOON",
        "5) K ROCKET: HOMING HEAVY HIT",
        "6) J BOMB: ARC + GROUND BLAST",
        "7) SPACE DASH: INVULN BURST",
        "8) P IS PAUSE",
        "TRAINING COMPLETE",
    ]
    return steps[min(getattr(game, 'tutorial_step', 0), len(steps) - 1)]
globals()['_tutorial_instruction_v15'] = _tutorial_instruction_v16
globals()['_tutorial_instruction_v13'] = _tutorial_instruction_v16
_old_draw_training_map_v16 = globals().get('_draw_training_map_v15') or globals().get('_draw_training_map_v13')
def _draw_training_map_v16(game):
    if _old_draw_training_map_v16:
        _old_draw_training_map_v16(game)
    pyxel.camera(0, 0)
    pyxel.rect(122, 5, 190, 31, C0)
    pyxel.rectb(122, 5, 190, 31, C12)
    pyxel.text(127, 10, "BLASTER: FAST BASIC SHOTS", C10)
    pyxel.text(127, 19, "SPREAD: WIDE BUT SLOWER", C15)
    pyxel.text(127, 28, "LASER: BOSS-FIGHT ONLY", C8)
globals()['_draw_training_map_v15'] = _draw_training_map_v16
globals()['_draw_training_map_v13'] = _draw_training_map_v16
def _draw_tutorial_v16(self):
    if not hasattr(self, 'tutorial_flags') or 'weapon' not in getattr(self, 'tutorial_flags', {}):
        _setup_tutorial_v15(self)
    _draw_training_map_v16(self)
Game.draw_tutorial = _draw_tutorial_v16
def _v16_bullet_style_for_key(key):
    try:
        return _empire_bullet_style_v15(key)
    except Exception:
        return 'empire_flag_bolt'
_old_player_fire_v16_bullets = Player.fire
def _player_fire_v16_bullets(self):
    bullets, effects = _old_player_fire_v16_bullets(self)
    game = globals().get('_active_secret_game', None)
    if game is not None and getattr(game, 'empire_mode', False):
        key = _v16_country_key(game)
        for b in bullets:
            if not getattr(b, 'empire_style', None):
                b.empire_style = _v16_bullet_style_for_key(key)
                b.empire_country = key
            if key != 'germany':
                b.vx *= 0.72
                b.vy *= 0.72
                b.life = int(max(getattr(b, 'life', 80), 120))
                b.radius = max(getattr(b, 'radius', 2), 4)
                b.empire_detail_variant = random.randint(0, 3)
                b.empire_seed = random.randint(0, 9999)
            else:
                b.empire_detail_variant = random.randint(0, 2)
    return bullets, effects
Player.fire = _player_fire_v16_bullets
_old_bullet_draw_v16_detail = Bullet.draw
def _bullet_draw_v16_detail(self, cam_x):
    style = getattr(self, 'empire_style', None)
    if not style or style == 'thick_yellow_gas':
        return _old_bullet_draw_v16_detail(self, cam_x)
    sx1, sy1 = int(self.px - cam_x), int(self.py)
    sx, sy = int(self.x - cam_x), int(self.y)
    if sx < -40 or sx > SCREEN_W + 40 or sy < -40 or sy > SCREEN_H + 40:
        return
    ang = math.atan2(self.y - self.py, self.x - self.px)
    ux, uy = math.cos(ang), math.sin(ang)
    pxp, pyp = -uy, ux
    seed = getattr(self, 'empire_seed', 0)
    var = getattr(self, 'empire_detail_variant', 0)
    def pt(ax, ay):
        return int(sx + ux * ax + pxp * ay), int(sy + uy * ax + pyp * ay)
    def line(ax, ay, bx, by, col):
        x1, y1 = pt(ax, ay); x2, y2 = pt(bx, by)
        pyxel.line(x1, y1, x2, y2, col)
    def circ(ax, ay, r, col, border=False):
        x, y = pt(ax, ay)
        (pyxel.circb if border else pyxel.circ)(x, y, r, col)
    if style == 'dragon_red_yellow':
        for i in range(7):
            wob = math.sin((pyxel.frame_count + seed + i * 8) * .28) * (3 + var)
            ax = -i * 5
            ay = wob
            circ(ax, ay, max(2, 5 - i // 2), C8 if i % 2 == 0 else C10)
            circ(ax, ay, max(2, 5 - i // 2), C7, border=True)
        hx, hy = pt(8, 0)
        pyxel.tri(hx, hy, *pt(-1, -7), *pt(-1, 7), C8)
        pyxel.tri(*pt(3, -4), *pt(7, -9), *pt(0, -5), C10)
        pyxel.tri(*pt(3, 4), *pt(7, 9), *pt(0, 5), C10)
        line(6, -2, 15, -6, C10); line(6, 2, 15, 6, C10)
        pyxel.pset(*pt(5, -2), C7); pyxel.pset(*pt(5, 2), C7)
        line(-30, 0, sx1 - sx, sy1 - sy, C10)
    elif style == 'eagle':
        flap = 8 + ((pyxel.frame_count + seed) // 3) % 4
        pyxel.tri(*pt(-2, 0), *pt(-14, -flap), *pt(-7, -2), C7)
        pyxel.tri(*pt(-2, 0), *pt(-14, flap), *pt(-7, 2), C7)
        pyxel.tri(*pt(-7, -2), *pt(-21, -flap - 2), *pt(-13, -1), C6)
        pyxel.tri(*pt(-7, 2), *pt(-21, flap + 2), *pt(-13, 1), C6)
        circ(-2, 0, 4, C7)
        pyxel.tri(*pt(4, 0), *pt(11, -3), *pt(11, 3), C10)
        pyxel.pset(*pt(3, -2), C0)
        if var % 2 == 0:
            for oy in (-7, 7):
                pyxel.pset(*pt(-17, oy), C12)
    elif style == 'royal_cannonball':
        circ(0, 0, 6, C5); circ(0, 0, 6, C10, border=True)
        circ(-2, -2, 2, C6)
        pyxel.rect(*pt(-5, -9), 10, 2, C10)
        pyxel.pset(*pt(-4, -12), C10); pyxel.pset(*pt(0, -13), C10); pyxel.pset(*pt(4, -12), C10)
        line(-9, 3, -22, 6, C6)
        for i in range(3):
            circ(-18 - i * 5, 5 + i % 2, 2, C6, border=(i == 2))
    elif style == 'laurel_spear':
        line(-22, 0, 11, 0, C7); line(-16, -1, 7, -1, C10)
        pyxel.tri(*pt(12, 0), *pt(3, -5), *pt(3, 5), C8)
        for i in range(5):
            ax = -18 + i * 5
            pyxel.tri(*pt(ax, -2), *pt(ax + 3, -6), *pt(ax + 5, -2), C3)
            pyxel.tri(*pt(ax, 2), *pt(ax + 3, 6), *pt(ax + 5, 2), C3)
    elif style == 'legionary_pilum':
        line(-26, 0, 12, 0, C4); line(-20, -1, 10, -1, C7)
        pyxel.rect(*pt(-6, -4), 7, 8, C8)
        pyxel.rectb(*pt(-6, -4), 7, 8, C10)
        pyxel.tri(*pt(13, 0), *pt(3, -5), *pt(3, 5), C7)
        pyxel.pset(*pt(-3, 0), C10)
    elif style == 'wolf_spirit':
        circ(0, 0, 7, C7); circ(0, 0, 8, C12, border=True)
        pyxel.tri(*pt(-5, -4), *pt(-2, -13), *pt(1, -4), C7)
        pyxel.tri(*pt(5, -4), *pt(2, -13), *pt(-1, -4), C7)
        pyxel.pset(*pt(-3, -1), C12); pyxel.pset(*pt(3, -1), C12)
        pyxel.tri(*pt(0, 2), *pt(-4, 6), *pt(4, 6), C6)
        for i in range(4):
            circ(-10 - i * 7, math.sin((pyxel.frame_count + seed + i * 5) * .25) * 4, 3, C12, border=True)
    elif style == 'gladiator_net':
        s = 10 + var
        pyxel.rectb(sx - s, sy - s, s * 2, s * 2, C7)
        for k in range(-s, s + 1, 4):
            pyxel.line(sx - s, sy + k, sx + s, sy + k, C6)
            pyxel.line(sx + k, sy - s, sx + k, sy + s, C6)
        pyxel.line(sx - s, sy - s, sx + s, sy + s, C10)
        pyxel.line(sx + s, sy - s, sx - s, sy + s, C10)
        pyxel.pset(sx, sy, C14)
    elif style == 'samurai_star':
        spin = ((pyxel.frame_count + seed) // 2) % 4
        r1, r2 = 9, 4
        if spin % 2:
            pyxel.line(*pt(-r1, 0), *pt(r1, 0), C7); pyxel.line(*pt(0, -r1), *pt(0, r1), C7)
            pyxel.line(*pt(-r2, -r2), *pt(r2, r2), C12); pyxel.line(*pt(-r2, r2), *pt(r2, -r2), C12)
        else:
            pyxel.line(*pt(-r1, -r1), *pt(r1, r1), C7); pyxel.line(*pt(-r1, r1), *pt(r1, -r1), C7)
            pyxel.line(*pt(-r1, 0), *pt(r1, 0), C12); pyxel.line(*pt(0, -r1), *pt(0, r1), C12)
        circ(0, 0, 3, C8); circ(0, 0, 8, C7, border=True)
    elif style == 'baguette':
        line(-20, 0, 13, 0, C10)
        line(-19, -2, 12, -2, C7); line(-19, 2, 12, 2, C4)
        for i in range(4):
            ax = -14 + i * 8
            line(ax, -4, ax + 5, 3, C4)
        for i in range(3 + var):
            circ(-22 - i * 4, random.choice([-3, 0, 3]), 1, C10)
    elif style == 'piranha_swarm':
        count = 6 + var
        for i in range(count):
            ox = -i * 4 + int(math.sin((pyxel.frame_count + seed + i * 11) * .32) * 6)
            oy = int(math.cos((pyxel.frame_count + seed + i * 13) * .29) * 7)
            color = C8 if i % 2 else C12
            pyxel.tri(*pt(ox - 5, oy), *pt(ox + 5, oy - 4), *pt(ox + 5, oy + 4), color)
            pyxel.tri(*pt(ox - 6, oy), *pt(ox - 10, oy - 3), *pt(ox - 10, oy + 3), C3)
            pyxel.pset(*pt(ox + 3, oy - 1), C7)
            pyxel.pset(*pt(ox + 5, oy + 1), C7)
    elif style == 'scimitar':
        circ(0, 0, 9, C10, border=True)
        circ(-4, -1, 9, C0)
        line(-5, 6, -14, 12, C5)
        line(-13, 11, -17, 8, C8)
        pyxel.pset(*pt(6, -5), C7)
        if var % 2:
            pyxel.circb(*pt(1, 0), 11, C14)
    elif style == 'bear_head':
        circ(0, 0, 7, C4); circ(0, 0, 8, C8, border=True)
        circ(-5, -6, 3, C4); circ(5, -6, 3, C4)
        pyxel.rect(*pt(-4, 2), 8, 4, C7)
        pyxel.pset(*pt(-3, -2), C0); pyxel.pset(*pt(3, -2), C0)
        pyxel.pset(*pt(0, 0), C0)
        pyxel.text(*pt(-4, -11), "*", C8)
        for i in range(3):
            circ(-13 - i * 5, 3 - i, 2, C6, border=True)
    elif style == 'empire_flag_bolt':
        line(-20, 0, 10, 0, C14)
        circ(0, 0, 5, C10)
        circ(0, 0, 8, C7, border=True)
        try:
            draw_flag(getattr(self, 'empire_country', 'usa'), sx - 8, sy - 5, 14, 9, pyxel.frame_count * .2)
        except Exception:
            pass
    else:
        return _old_bullet_draw_v16_detail(self, cam_x)
Bullet.draw = _bullet_draw_v16_detail
def _draw_mini_bullet_thing_v16(style, x, y, seed):
    kind = style
    if kind == 'italy_random':
        kind = random.choice(_ITALY_BULLETS_V15) if '_ITALY_BULLETS_V15' in globals() else 'laurel_spear'
    if kind == 'dragon_red_yellow':
        pyxel.circ(x, y, 4, C8); pyxel.circ(x - 5, y + 2, 3, C10); pyxel.tri(x + 5, y, x + 10, y - 4, x + 10, y + 4, C8)
    elif kind == 'eagle':
        pyxel.line(x - 8, y, x, y - 3, C7); pyxel.line(x, y - 3, x + 8, y, C7); pyxel.tri(x + 4, y, x + 9, y - 2, x + 9, y + 2, C10)
    elif kind == 'royal_cannonball':
        pyxel.circ(x, y, 4, C5); pyxel.circb(x, y, 4, C10); pyxel.pset(x, y - 6, C10)
    elif kind in ('laurel_spear', 'legionary_pilum'):
        pyxel.line(x - 9, y + 3, x + 9, y - 3, C7); pyxel.tri(x + 10, y - 3, x + 5, y - 7, x + 5, y + 1, C8)
    elif kind == 'wolf_spirit':
        pyxel.circ(x, y, 5, C7); pyxel.tri(x - 4, y - 2, x - 1, y - 8, x + 1, y - 2, C7); pyxel.pset(x - 2, y, C12)
    elif kind == 'gladiator_net':
        pyxel.rectb(x - 6, y - 6, 12, 12, C7); pyxel.line(x - 6, y, x + 6, y, C6); pyxel.line(x, y - 6, x, y + 6, C6)
    elif kind == 'samurai_star':
        pyxel.line(x - 7, y, x + 7, y, C7); pyxel.line(x, y - 7, x, y + 7, C7); pyxel.line(x - 5, y - 5, x + 5, y + 5, C12); pyxel.line(x - 5, y + 5, x + 5, y - 5, C12)
    elif kind == 'baguette':
        pyxel.line(x - 8, y + 3, x + 8, y - 3, C10); pyxel.line(x - 6, y + 1, x + 6, y - 5, C7); pyxel.pset(x, y, C4)
    elif kind == 'piranha_swarm':
        for i in range(3):
            pyxel.tri(x - 5 + i * 4, y + i % 2, x + i * 4, y - 3, x + i * 4, y + 3, C8 if i % 2 else C12)
    elif kind == 'scimitar':
        pyxel.circb(x, y, 7, C10); pyxel.circ(x - 3, y - 1, 7, C0); pyxel.line(x - 4, y + 5, x - 9, y + 9, C5)
    elif kind == 'bear_head':
        pyxel.circ(x, y, 5, C4); pyxel.circ(x - 4, y - 4, 2, C4); pyxel.circ(x + 4, y - 4, 2, C4); pyxel.pset(x - 2, y - 1, C0)
    elif kind == 'thick_yellow_gas':
        pyxel.circ(x - 4, y, 4, C10); pyxel.circ(x + 1, y - 3, 5, C10); pyxel.circb(x, y, 7, C4)
    else:
        pyxel.circ(x, y, 4, C14); pyxel.circb(x, y, 6, C7)
_old_player_draw_v16_trail = Player.draw
def _player_draw_v16_trail(self, cam_x):
    dt = getattr(self, 'dash_timer', 0)
    if dt <= 0:
        return _old_player_draw_v16_trail(self, cam_x)
    self.dash_timer = 0
    try:
        result = _old_player_draw_v16_trail(self, cam_x)
    finally:
        self.dash_timer = dt
    game = globals().get('_active_secret_game', None)
    key = _v16_country_key(game if game is not None else self)
    base_style = _v16_bullet_style_for_key(key)
    styles = [base_style, base_style, base_style, 'thick_yellow_gas', 'samurai_star', 'royal_cannonball', 'baguette', 'piranha_swarm']
    sx = int(self.x - cam_x)
    sy = int(self.y + 12)
    direction = -getattr(self, 'facing', 1)
    rng = random.Random((pyxel.frame_count // 2) + int(self.x * 7) + int(self.y * 11))
    for i in range(7 if (game is not None and getattr(game, 'empire_mode', False)) else 5):
        tx = sx + direction * (14 + i * 12) + rng.randint(-2, 2)
        ty = sy + rng.randint(-7, 7)
        st = rng.choice(styles)
        _draw_mini_bullet_thing_v16(st, tx, ty, rng.randint(0, 9999))
        if i < 5:
            pyxel.line(tx, ty, sx - getattr(self, 'facing', 1) * 4, sy, C14 if i % 2 else C10)
    return result
Player.draw = _player_draw_v16_trail
_old_next_level_v16 = Game.next_level
def _next_level_v16(self):
    self.earthquake_warning_timer = 0
    self.quake_red_border_timer = 0
    result = _old_next_level_v16(self)
    self.earthquake_warning_timer = 0
    self.quake_red_border_timer = 0
    return result
Game.next_level = _next_level_v16
_old_reset_run_v16 = Game.reset_run
def _reset_run_v16(self):
    result = _old_reset_run_v16(self)
    self.earthquake_warning_timer = 0
    self.quake_red_border_timer = 0
    self.landmine_spawn_cd = FPS * 3
    return result
Game.reset_run = _reset_run_v16
FINAL_BUILD_TAG = "v16-quake-balance-readable-empire-bullets"
def _v17_quake_active(game):
    return bool(game is not None and (getattr(game, 'earthquake_timer', 0) > 0 or getattr(game, 'earthquake_warning_timer', 0) > 0))
def _v17_empire_active(game):
    return bool(game is not None and getattr(game, 'empire_mode', False) and not getattr(game, 'empire_won', False))
_old_ensure_v14_state_v17 = globals().get('_ensure_v14_state')
def _ensure_v14_state_v17(game):
    if _old_ensure_v14_state_v17:
        _old_ensure_v14_state_v17(game)
    if not hasattr(game, 'empire_boss_tip_shown'):
        game.empire_boss_tip_shown = False
    if not hasattr(game, 'empire_boss_tip_timer'):
        game.empire_boss_tip_timer = 0
    if not hasattr(game, 'empire_reach_boss_bonus_claimed'):
        game.empire_reach_boss_bonus_claimed = False
    if not hasattr(game, 'quake_impact_flash_timer'):
        game.quake_impact_flash_timer = 0
    if not hasattr(game, 'quake_enemy_suppression_timer'):
        game.quake_enemy_suppression_timer = 0
globals()['_ensure_v14_state'] = _ensure_v14_state_v17
def _draw_red_danger_border_v16(label, color=C8):
    label = '' if label is None else str(label)
    hp_border_only = label.upper().startswith('1 HP')
    quake_label = ('QUAKE' in label.upper()) or ('EARTHQUAKE' in label.upper())
    pulse = (pyxel.frame_count // (2 if quake_label else 3)) % 2 == 0
    thickness = 5 if quake_label else 2
    for i in range(thickness):
        pyxel.rectb(1 + i, 25 + i, SCREEN_W - 2 - i * 2, SCREEN_H - 27 - i * 2, color)
    if pulse:
        for i in range(2 if quake_label else 1):
            pyxel.rectb(8 + i * 2, 32 + i * 2, SCREEN_W - 16 - i * 4, SCREEN_H - 41 - i * 4, C14 if quake_label else color)
    if hp_border_only or not label:
        return
    if quake_label:
        pyxel.rect(36, 24, 248, 20, C8)
        pyxel.rectb(34, 22, 252, 24, C7)
        pyxel.text(55, 29, label[:34], C7)
        if pulse:
            pyxel.text(68, 49, '!!! GROUND BREAKING - JUMP GAPS !!!', C10)
            pyxel.text(69, 50, '!!! GROUND BREAKING - JUMP GAPS !!!', C0)
    else:
        pyxel.rect(97, 25, 126, 12, C0)
        pyxel.rectb(97, 25, 126, 12, color)
        pyxel.text(103, 29, label[:26], color)
globals()['_draw_red_danger_border_v16'] = _draw_red_danger_border_v16
_old_difficulty_value_v17 = Game.difficulty_value
def _difficulty_value_v17(self):
    base = _old_difficulty_value_v17(self)
    try:
        if (self.is_campaign() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == 'CAMPAIGN ROTATION')) and (getattr(self, 'empire_mode', False) or int(getattr(self, 'level', 1)) > CAMPAIGN_LEVELS):
            return max(4.65, base * 0.90 - 0.15)
    except Exception:
        pass
    return base
Game.difficulty_value = _difficulty_value_v17
_old_danger_scale_v17 = Game.danger_scale
def _danger_scale_v17(self):
    base = _old_danger_scale_v17(self)
    try:
        if (self.is_campaign() or (self.is_multiplayer() and self.mp_mode_names[self.mp_mode_index] == 'CAMPAIGN ROTATION')) and (getattr(self, 'empire_mode', False) or int(getattr(self, 'level', 1)) > CAMPAIGN_LEVELS):
            return max(1.35, base * 0.88 - 0.05)
    except Exception:
        pass
    return base
Game.danger_scale = _danger_scale_v17
def _v17_spawn_mult(game, normal=1.0, quake=0.45, empire=0.90):
    mult = normal
    if _v17_quake_active(game):
        mult *= quake
    if _v17_empire_active(game):
        mult *= empire
    return mult
def _missile_spawn_chance_v17(self):
    try:
        base = _old_missile_chance_v14(self)
    except Exception:
        base = min(0.006 + self.danger_scale() * 0.005, 0.12)
    return min(base * _v17_spawn_mult(self, normal=1.02, quake=0.42, empire=0.88), 0.14)
def _drone_spawn_chance_v17(self):
    try:
        base = _old_drone_chance_v14(self)
    except Exception:
        base = min(0.002 + self.danger_scale() * 0.0026, 0.04)
    return min(base * _v17_spawn_mult(self, normal=0.96, quake=0.35, empire=0.88), 0.048)
def _turret_spawn_chance_v17(self):
    try:
        base = _old_turret_chance_v14(self)
    except Exception:
        d = self.difficulty_value()
        base = 0 if d < 2.2 else min(0.001 + d * 0.0014, 0.012)
    return min(base * _v17_spawn_mult(self, normal=0.90, quake=0.38, empire=0.86), 0.014)
def _bomber_spawn_chance_v17(self):
    try:
        base = _old_bomber_chance_v14(self)
    except Exception:
        d = self.difficulty_value()
        base = 0 if d < 4.8 else min(0.00035 + d * 0.00022, 0.0024)
    d = self.difficulty_value()
    if d < 2.25:
        return 0
    boosted = min(max(base * 3.0, 0.00075 + d * 0.00013), 0.0068)
    return boosted * _v17_spawn_mult(self, normal=1.0, quake=0.45, empire=0.86)
Game.missile_spawn_chance = _missile_spawn_chance_v17
Game.drone_spawn_chance = _drone_spawn_chance_v17
Game.turret_spawn_chance = _turret_spawn_chance_v17
Game.bomber_spawn_chance = _bomber_spawn_chance_v17
_old_maybe_spawn_landmine_v17 = globals().get('_maybe_spawn_landmine_v14')
def _maybe_spawn_landmine_v17(game):
    _ensure_v14_state(game)
    if _v17_quake_active(game):
        game.landmine_spawn_cd = max(getattr(game, 'landmine_spawn_cd', FPS * 3), FPS * 4)
        return
    if _old_maybe_spawn_landmine_v17:
        return _old_maybe_spawn_landmine_v17(game)
globals()['_maybe_spawn_landmine_v14'] = _maybe_spawn_landmine_v17
def _terrain_add_uplift_v17(terrain, x):
    if not hasattr(terrain, 'uplifts'):
        terrain.uplifts = []
    terrain.uplifts.append({
        'x': x,
        'r': random.randint(30, 64),
        'height': random.randint(14, 30),
    })
    if len(terrain.uplifts) > 48:
        terrain.uplifts.pop(0)
def _begin_earthquake_warning_v17(game):
    _ensure_v14_state(game)
    if getattr(game, 'earthquake_warning_timer', 0) > 0 or getattr(game, 'earthquake_timer', 0) > 0:
        return
    game.earthquake_warning_timer = int(FPS * 2.4)
    game.quake_red_border_timer = game.earthquake_warning_timer
    game.quake_impact_flash_timer = max(getattr(game, 'quake_impact_flash_timer', 0), FPS)
    game.add_text(game.player.x + 26, 34, '!!! EARTHQUAKE WARNING !!!', C8)
    game.add_shake(34, 5)
    sfx(0, 6)
globals()['_begin_earthquake_warning_v16'] = _begin_earthquake_warning_v17
def _start_earthquake_v17(game):
    _ensure_v14_state(game)
    if game.earthquake_timer > 0:
        return
    game.earthquake_timer = random.randint(int(FPS * 6.2), int(FPS * 8.0))
    game.earthquake_trench_cd = 1
    game.earthquake_missile_cd = random.randint(24, 42)
    game.earthquake_recent_x = []
    game.earthquake_fast_phase = 0
    game.quake_red_border_timer = max(getattr(game, 'quake_red_border_timer', 0), game.earthquake_timer)
    game.quake_impact_flash_timer = FPS * 2
    game.quake_enemy_suppression_timer = game.earthquake_timer
    game.earthquake_next_level = int(getattr(game, 'level', 1)) + random.choice([1, 2])
    game.add_text(game.player.x + 20, 38, '!!! EARTHQUAKE HITS !!!', C8)
    game.add_shake(game.earthquake_timer, 10)
    sfx(0, 7)
globals()['_start_earthquake_v14'] = _start_earthquake_v17
def _update_earthquake_v17(game):
    _ensure_v14_state(game)
    if not _campaign_like_v14(game) or getattr(game, 'state', '') != 'playing':
        return
    level_len = _level_len_v14(game)
    if getattr(game, 'earthquake_warning_timer', 0) > 0:
        game.earthquake_warning_timer -= 1
        game.quake_red_border_timer = max(getattr(game, 'quake_red_border_timer', 0), game.earthquake_warning_timer)
        game.quake_impact_flash_timer = max(getattr(game, 'quake_impact_flash_timer', 0), 8)
        game.shake_x = random.choice([-4, -3, -2, 2, 3, 4])
        game.shake_y = random.choice([-2, -1, 1, 2])
        game.shake_timer = max(getattr(game, 'shake_timer', 0), 2)
        if game.earthquake_warning_timer % 34 == 0:
            game.add_text(game.player.x + random.randint(15, 80), 58, 'WARNING!', C8)
        if game.earthquake_warning_timer <= 0:
            _start_earthquake_v17(game)
        return
    if game.earthquake_timer <= 0:
        if int(getattr(game, 'level', 1)) >= int(getattr(game, 'earthquake_next_level', 2)) and 0.18 < game.distance / max(1, level_len) < 0.82:
            _begin_earthquake_warning_v17(game)
        return
    game.earthquake_timer -= 1
    game.quake_red_border_timer = max(getattr(game, 'quake_red_border_timer', 0), game.earthquake_timer)
    game.quake_enemy_suppression_timer = max(getattr(game, 'quake_enemy_suppression_timer', 0), game.earthquake_timer)
    game.earthquake_fast_phase = getattr(game, 'earthquake_fast_phase', 0) + 1
    phase = game.earthquake_fast_phase
    game.shake_x = random.choice([-11, -9, -7, -5, 5, 7, 9, 11]) + (1 if phase % 2 == 0 else -1) * random.randint(0, 3)
    game.shake_y = random.choice([-7, -5, -3, 3, 5, 7])
    if phase % 3 == 0:
        game.shake_x = -game.shake_x
    game.shake_timer = max(getattr(game, 'shake_timer', 0), 2)
    game.shake_power = max(getattr(game, 'shake_power', 0), 9)
    game.earthquake_missile_cd -= 1
    if game.earthquake_missile_cd <= 0:
        _spawn_quake_missile_v16(game)
        game.earthquake_missile_cd = random.randint(26, 48)
    game.earthquake_trench_cd -= 1
    if game.earthquake_trench_cd <= 0:
        game.earthquake_trench_cd = random.randint(9, 17)
        tx = _pick_quake_x_v16(game)
        game.earthquake_recent_x = (getattr(game, 'earthquake_recent_x', []) + [tx])[-16:]
        gy = game.terrain.base_ground_y_at(tx, game.difficulty_value())
        if random.random() < 0.48:
            _terrain_add_jumpable_trench_v16(game.terrain, tx, random.randint(18, 27))
            game.spawn_explosion(tx, gy, random.randint(10, 17), 'normal')
            game.texts.append(FlashText(tx - random.randint(6, 15), gy - random.randint(27, 43), '!', C8, life=50))
            if random.random() < 0.70:
                game.texts.append(FlashText(tx + random.randint(10, 25), gy - random.randint(22, 40), 'BREAK!', C14, life=42))
            for _p in range(16):
                game.particles.append(Particle(tx, gy, random.uniform(-2.7, 2.7), random.uniform(-3.5, -0.2), random.choice([C4, C5, C6, C7, C10]), random.randint(18, 46), size=random.choice([1, 1, 2]), gravity=.11))
        else:
            _terrain_add_uplift_v17(game.terrain, tx)
            game.spawn_explosion(tx, gy - 9, random.randint(8, 13), 'muzzle')
            game.texts.append(FlashText(tx, gy - random.randint(34, 52), '!', C10, life=42))
            for _p in range(14):
                game.particles.append(Particle(tx, gy - 6, random.uniform(-2.0, 2.0), random.uniform(-3.0, -0.3), random.choice([C3, C4, C5, C10]), random.randint(16, 38), size=random.choice([1, 1, 2]), gravity=.10))
        if random.random() < 0.50:
            _terrain_add_uplift_v17(game.terrain, _pick_quake_x_v16(game))
globals()['_update_earthquake_v14'] = _update_earthquake_v17
def _draw_earthquake_overlay_v17(game):
    _ensure_v14_state(game)
    warning = getattr(game, 'earthquake_warning_timer', 0)
    active = getattr(game, 'earthquake_timer', 0)
    if warning <= 0 and active <= 0:
        return
    pyxel.camera(0, 0)
    if warning > 0:
        sec = max(1, int(math.ceil(warning / FPS)))
        _draw_red_danger_border_v16('!!! QUAKE WARNING ' + str(sec) + ' !!!', C8)
        if (pyxel.frame_count // 5) % 2 == 0:
            pyxel.rect(51, 65, 218, 17, C8)
            pyxel.text(61, 70, 'RED ALERT: TRENCHES WILL OPEN', C7)
    else:
        _draw_red_danger_border_v16('!!! EARTHQUAKE - DASH / JUMP !!!', C8)
        if (pyxel.frame_count // 3) % 2 == 0:
            pyxel.rect(42, 62, 236, 19, C8)
            pyxel.rectb(40, 60, 240, 23, C7)
            pyxel.text(56, 68, '!!! MOVE BETWEEN THE CRACKS !!!', C7)
globals()['_draw_earthquake_overlay_v14'] = _draw_earthquake_overlay_v17
_old_player_hurtbox_v17 = Player.hurtbox
def _player_hurtbox_v17(self):
    game = globals().get('_active_secret_game', None)
    if _v17_quake_active(game):
        return self.x + 5, self.y + 5, max(4, PLAYER_W - 10), max(6, PLAYER_H - 10)
    return _old_player_hurtbox_v17(self)
Player.hurtbox = _player_hurtbox_v17
_old_player_fire_v17_cooldown = Player.fire
def _player_fire_v17_cooldown(self):
    fired_weapon = self.weapon
    bullets, effects = _old_player_fire_v17_cooldown(self)
    if bullets:
        game = globals().get('_active_secret_game', None)
        extra = 0
        if fired_weapon == 'BLASTER':
            extra = 5
        elif fired_weapon == 'SPREAD':
            extra = 13
        elif fired_weapon == 'LASER':
            extra = 10
        if _v17_empire_active(game):
            extra += 3
        self.shot_cd = max(getattr(self, 'shot_cd', 0), int(getattr(self, 'shot_cd', 0)) + extra)
    return bullets, effects
Player.fire = _player_fire_v17_cooldown
_old_player_update_v17_movement = Player.update
def _player_update_v17_movement(self, difficulty, dev_mode=False):
    game = globals().get('_active_secret_game', None)
    if _v17_empire_active(game):
        self.speed = max(getattr(self, 'speed', 1.9), 2.16)
        self.air_speed = max(getattr(self, 'air_speed', 1.6), 1.92)
        self.jump_power = min(getattr(self, 'jump_power', -5.5), -5.84)
        self.max_jumps = max(getattr(self, 'max_jumps', 2), 2)
        if getattr(self, 'dash_cd', 0) > 0 and self.dash_timer <= 0 and pyxel.frame_count % 5 == 0:
            self.dash_cd = max(0, self.dash_cd - 1)
    return _old_player_update_v17_movement(self, difficulty, dev_mode)
Player.update = _player_update_v17_movement
_old_update_field_order_v17 = globals().get('_update_field_order')
def _update_field_order_v17(game):
    _ensure_v14_state(game)
    if _v16_boss_active(game) or _v17_quake_active(game):
        try:
            game.field_order = None
            game.field_order_progress = 0
            game.field_order_timer = 0
            game.field_order_cooldown = max(getattr(game, 'field_order_cooldown', 0), FPS * (9 if _v17_quake_active(game) else 8))
        except Exception:
            pass
        return
    if _old_update_field_order_v17:
        return _old_update_field_order_v17(game)
globals()['_update_field_order'] = _update_field_order_v17
_old_draw_v12_radio_mission_v17 = globals().get('_draw_v12_radio_mission')
def _draw_v12_radio_mission_v17(game):
    _v12_safe_field_order_state(game)
    if _v16_boss_active(game) or _v17_quake_active(game):
        return
    if _v17_empire_active(game):
        if not game.field_order:
            if getattr(game, 'field_order_cooldown', 9999) < 2 * FPS:
                pyxel.text(86, 29, 'radio order incoming', C1)
                pyxel.text(85, 28, 'radio order incoming', C12)
            return
        o = game.field_order
        code = str(o.get('code', 'ORDER'))[:12]
        brief = str(o.get('brief', 'Complete'))[:24]
        goal = max(1, int(o.get('goal', 1)))
        prog = max(0, int(getattr(game, 'field_order_progress', 0)))
        x, y = 78, 27
        pyxel.text(x + 1, y + 1, 'RADIO: ' + code, C0)
        pyxel.text(x, y, 'RADIO: ' + code, C12)
        pyxel.text(x + 1, y + 10, brief, C0)
        pyxel.text(x, y + 9, brief, C7)
        pyxel.line(x, y + 21, x + 118, y + 21, C1)
        pyxel.line(x, y + 21, x + int(118 * clamp(prog / goal, 0, 1)), y + 21, C12)
        pyxel.text(x + 126, y + 7, str(prog) + '/' + str(goal), C7)
        return
    if _old_draw_v12_radio_mission_v17:
        return _old_draw_v12_radio_mission_v17(game)
globals()['_draw_v12_radio_mission'] = _draw_v12_radio_mission_v17
def _v17_apply_empire_boss_bonus(game):
    _ensure_v14_state(game)
    if not _v17_empire_active(game):
        return
    boss_alive = any(isinstance(e, BossEnemy) and getattr(e, 'alive', True) for e in getattr(game, 'enemies', []))
    if not boss_alive:
        return
    if not getattr(game, 'empire_boss_tip_shown', False):
        game.empire_boss_tip_shown = True
        game.empire_boss_tip_timer = FPS * 6
        game.empire_reach_boss_bonus_claimed = True
        game.player.max_hp = max(getattr(game.player, 'max_hp', 1), 2)
        game.player.hp = max(getattr(game.player, 'hp', 1), 2)
        game.player.invuln = max(getattr(game.player, 'invuln', 0), FPS)
        game.add_text(game.player.x + 12, 36, 'QUEST COMPLETE: REACH BOSS  +1 HP', C10)
        game.add_text(game.player.x + 12, 50, 'DASH 1 SECOND BEFORE LASER!', C14)
    elif getattr(game, 'empire_reach_boss_bonus_claimed', False) and getattr(game.player, 'max_hp', 1) < 2:
        game.player.max_hp = 2
        game.player.hp = max(getattr(game.player, 'hp', 1), 1)
_old_update_playing_v17_boss_tip = Game.update_playing
def _update_playing_v17_boss_tip(self):
    globals()['_active_secret_game'] = self
    result = _old_update_playing_v17_boss_tip(self)
    _ensure_v14_state(self)
    if getattr(self, 'empire_boss_tip_timer', 0) > 0:
        self.empire_boss_tip_timer -= 1
    _v17_apply_empire_boss_bonus(self)
    if _v17_empire_active(self) and getattr(self, 'empire_reach_boss_bonus_claimed', False):
        if any(isinstance(e, BossEnemy) and getattr(e, 'alive', True) for e in getattr(self, 'enemies', [])):
            self.player.max_hp = max(getattr(self.player, 'max_hp', 1), 2)
            self.player.hp = max(getattr(self.player, 'hp', 1), min(2, getattr(self.player, 'hp', 1)))
    return result
Game.update_playing = _update_playing_v17_boss_tip
_old_draw_world_v17_tip = Game.draw_world
def _draw_world_v17_tip(self):
    _old_draw_world_v17_tip(self)
    _ensure_v14_state(self)
    try:
        if getattr(self, 'empire_boss_tip_timer', 0) > 0:
            pyxel.camera(0, 0)
            pyxel.rect(20, 52, 280, 54, C0)
            pyxel.rectb(20, 52, 280, 54, C10)
            pyxel.rectb(23, 55, 274, 48, C14 if (pyxel.frame_count // 6) % 2 == 0 else C8)
            pyxel.text(40, 62, 'QUEST COMPLETE: REACH BOSS FIGHT', C10)
            pyxel.text(55, 75, '+1 HEALTH FOR THIS EMPIRE BOSS', C7)
            pyxel.text(38, 89, 'LASER TIP: DASH ABOUT 1 SECOND BEFORE FIRE', C14)
        elif _v17_empire_active(self) and any(isinstance(e, BossEnemy) and getattr(e, 'alive', True) for e in getattr(self, 'enemies', [])):
            pyxel.camera(0, 0)
            pyxel.text(75, 26, 'LASER: DASH AT THE LAST SECOND', C14)
    except Exception:
        pass
Game.draw_world = _draw_world_v17_tip
_old_reset_run_v17 = Game.reset_run
def _reset_run_v17(self):
    result = _old_reset_run_v17(self)
    self.empire_boss_tip_shown = False
    self.empire_boss_tip_timer = 0
    self.empire_reach_boss_bonus_claimed = False
    self.quake_impact_flash_timer = 0
    self.quake_enemy_suppression_timer = 0
    return result
Game.reset_run = _reset_run_v17
_old_next_level_v17 = Game.next_level
def _next_level_v17(self):
    result = _old_next_level_v17(self)
    _ensure_v14_state(self)
    self.empire_boss_tip_timer = 0
    self.quake_impact_flash_timer = 0
    self.quake_enemy_suppression_timer = 0
    if _v17_empire_active(self) and getattr(self, 'empire_reach_boss_bonus_claimed', False):
        self.player.max_hp = max(getattr(self.player, 'max_hp', 1), 1)
    return result
Game.next_level = _next_level_v17
_old_draw_story_update_v17 = Game.draw_story_update
def _draw_story_update_v17(self):
    _ensure_empire_state(self)
    if getattr(self, 'empire_mode', False) and not getattr(self, 'empire_won', False):
        pyxel.cls(C0)
        key = self.current_country()['key']
        pyxel.rect(14, 12, 292, 176, C1)
        pyxel.rectb(14, 12, 292, 176, C10)
        pyxel.rectb(18, 16, 284, 168, C7)
        pyxel.text(95, 25, 'STORY UPDATE', C14)
        pyxel.text(82, 37, _empire_name(self)[:28], C10)
        try:
            draw_country_outline(key, 42, 60, scale=2, color=C14, fill=False)
            _draw_empire_flag(key, 216, 58, 48, 30, pyxel.frame_count * .18, True)
        except Exception:
            draw_flag(key, 216, 58, 48, 30, pyxel.frame_count * .18)
        bank = globals().get('EMPIRE_STORYLINES', {}).get(key)
        if bank:
            lines = bank[getattr(self, 'story_progress_index', 0) % len(bank)]
        else:
            lines = ['THE EMPIRE PRESSES FORWARD.', 'THE FRONTLINE SHIFTS AGAIN.', 'YOUR COMMANDERS AWAIT ORDERS.']
        y = 110
        for line in lines[:3]:
            pyxel.text(35, y, str(line)[:42], C7)
            y += 13
        pyxel.text(86, 169, 'PRESS ENTER TO CONTINUE', C10)
        return
    return _old_draw_story_update_v17(self)
Game.draw_story_update = _draw_story_update_v17
_old_draw_empire_flag_v17 = globals().get('_draw_empire_flag')
def _draw_empire_flag_v17(key, x, y, w=30, h=18, wave=0, empire=True):
    if key == 'germany' and empire:
        ox = int(math.sin(wave) * 1.0)
        x = int(x + ox); y = int(y); w = int(w); h = int(h)
        pyxel.rect(x, y, w, h, C8)
        pyxel.rectb(x, y, w, h, C7)
        cx = x + w // 2
        cy = y + h // 2
        r = max(3, min(w, h) // 3)
        pyxel.circ(cx, cy, r, C7)
        er = max(1, r // 5)
        pyxel.circ(cx - max(2, r // 3), cy - max(1, r // 4), er, C0)
        pyxel.circ(cx + max(2, r // 3), cy - max(1, r // 4), er, C0)
        mouth_y = cy + max(1, r // 4)
        pyxel.line(cx - max(2, r // 2), mouth_y, cx - max(1, r // 4), mouth_y + 1, C0)
        pyxel.line(cx - max(1, r // 4), mouth_y + 1, cx + max(1, r // 4), mouth_y + 1, C0)
        pyxel.line(cx + max(1, r // 4), mouth_y + 1, cx + max(2, r // 2), mouth_y, C0)
        return
    if _old_draw_empire_flag_v17:
        return _old_draw_empire_flag_v17(key, x, y, w, h, wave, empire)
    return draw_flag(key, x, y, w, h, wave)
globals()['_draw_empire_flag'] = _draw_empire_flag_v17
def _draw_empire_ascension_v17(self):
    pyxel.cls(C0)
    _ensure_empire_state(self)
    t = getattr(self, 'empire_anim_timer', getattr(self, 'empire_ascension_timer', getattr(self, 'intro_timer', 0)))
    key = self.current_country()['key']
    name = REAL_EMPIRE_NAMES.get(key, self.current_country()['name'] + ' EMPIRE') if 'REAL_EMPIRE_NAMES' in globals() else self.current_country()['name'] + ' EMPIRE'
    for yy in range(0, SCREEN_H, 5):
        pyxel.line(0, yy, SCREEN_W, yy, C1 if (yy * 2 + t) % 34 < 15 else C0)
    pyxel.rect(8, 8, 304, 184, C0)
    pyxel.rectb(8, 8, 304, 184, C10 if (t // 10) % 2 else C14)
    pyxel.text(70, 18, 'EMPIRE ASCENSION ONLINE', C14)
    pyxel.text(76, 31, name[:28], C10)
    p = clamp(t / 520.0, 0, 1)
    labels = [('TERRITORY', C3, p), ('MILITARY', C8, min(1, p * 1.12)), ('WEAPON CORE', C14, min(1, .40 + p * .60))]
    for i, (lab, col, val) in enumerate(labels):
        yy = 54 + i * 22
        pyxel.text(24, yy, lab, C7)
        pyxel.rect(92, yy, 106, 7, C1)
        pyxel.rectb(92, yy, 106, 7, C7)
        pyxel.rect(93, yy + 1, int(104 * val), 5, col)
    cx, cy = 160, 125
    pyxel.rect(cx - 31, cy - 50, 62, 78, C1)
    pyxel.rectb(cx - 31, cy - 50, 62, 78, C10)
    for i in range(7):
        pyxel.line(cx - 28, cy - 43 + i * 12, cx + 28, cy - 43 + i * 12 + int(math.sin((t + i * 11) / 10) * 5), C5)
    try:
        _draw_skin_preview(cx - 8, cy - 4, self.skin_index, key, empire=t > 150)
    except Exception:
        self.player.draw(self.cam_x)
    try:
        _draw_empire_flag(key, 222, 58, 46, 27, t * .2, True)
    except Exception:
        draw_flag(key, 222, 58, 46, 27, t * .2)
    pyxel.text(208, 95, 'CUSTOM BULLETS', C10)
    pyxel.text(204, 109, 'BULLET DASH TRAIL', C14)
    pyxel.text(50, 157, 'STATS: SPEED +0.26   JUMP +0.34   BOSS QUEST +HP', C10)
    pyxel.text(66, 169, 'EMPIRE WEAPONS ARE NOW ACTIVE', C7)
    if t > 390:
        pyxel.text(84, 184, 'PRESS ENTER TO DEPLOY', C14)
Game.draw_empire_ascension = _draw_empire_ascension_v17
_old_ensure_v14_state_v18 = globals().get('_ensure_v14_state')
def _ensure_v14_state_v18(game):
    if _old_ensure_v14_state_v18:
        _old_ensure_v14_state_v18(game)
    if game is None:
        return
    if not hasattr(game, 'empire_boss_tip_pause'):
        game.empire_boss_tip_pause = False
    if not hasattr(game, 'empire_boss_tip_resume_flash'):
        game.empire_boss_tip_resume_flash = 0
    if not hasattr(game, '_laser_dodge_text_cd'):
        game._laser_dodge_text_cd = 0
globals()['_ensure_v14_state'] = _ensure_v14_state_v18
_old_draw_red_danger_border_v18 = globals().get('_draw_red_danger_border_v16')
def _draw_red_danger_border_v16(label, color=C8):
    label = '' if label is None else str(label)
    if label.upper().startswith('1 HP'):
        if (pyxel.frame_count // 10) % 2 == 0:
            y0 = 25
            y1 = SCREEN_H - 2
            x1 = SCREEN_W - 1
            l = 34
            pyxel.line(1, y0, l, y0, color)
            pyxel.line(1, y0, 1, y0 + l, color)
            pyxel.line(x1 - l, y0, x1, y0, color)
            pyxel.line(x1, y0, x1, y0 + l, color)
            pyxel.line(1, y1, l, y1, color)
            pyxel.line(1, y1 - l, 1, y1, color)
            pyxel.line(x1 - l, y1, x1, y1, color)
            pyxel.line(x1, y1 - l, x1, y1, color)
        return
    if _old_draw_red_danger_border_v18:
        return _old_draw_red_danger_border_v18(label, color)
globals()['_draw_red_danger_border_v16'] = _draw_red_danger_border_v16
_old_player_fire_v18 = Player.fire
def _player_fire_v18(self):
    fired_weapon = self.weapon
    bullets, effects = _old_player_fire_v18(self)
    if bullets:
        relief = 2 if fired_weapon in ('BLASTER', 'SPREAD') else 1
        game = globals().get('_active_secret_game', None)
        if _v17_empire_active(game):
            relief = max(1, relief - 0)
        self.shot_cd = max(4, int(getattr(self, 'shot_cd', 0)) - int(relief))
    return bullets, effects
Player.fire = _player_fire_v18
_old_player_update_v18_dash = Player.update
def _player_update_v18_dash(self, difficulty, dev_mode=False):
    before_dash = getattr(self, 'dash_timer', 0)
    result = _old_player_update_v18_dash(self, difficulty, dev_mode)
    if not hasattr(self, 'boss_laser_dash_grace'):
        self.boss_laser_dash_grace = 0
    if getattr(self, 'dash_timer', 0) > 0 or before_dash > 0:
        self.boss_laser_dash_grace = FPS
    elif self.boss_laser_dash_grace > 0:
        self.boss_laser_dash_grace -= 1
    return result
Player.update = _player_update_v18_dash
def _v18_active_boss_laser(game):
    try:
        for e in getattr(game, 'enemies', []):
            if isinstance(e, BossEnemy) and getattr(e, 'alive', True):
                if getattr(e, 'laser_timer', 0) > 0:
                    return e
                if 0 < getattr(e, 'super_laser_timer', 0) <= 36:
                    return e
    except Exception:
        pass
    return None
_old_player_hit_v18 = Game.player_hit
def _player_hit_v18(self, dmg=1):
    _ensure_v14_state(self)
    boss = _v18_active_boss_laser(self)
    if boss is not None and getattr(self.player, 'boss_laser_dash_grace', 0) > 0:
        self.player.invuln = max(getattr(self.player, 'invuln', 0), 24)
        self.player.boss_laser_dash_grace = 0
        if getattr(self, '_laser_dodge_text_cd', 0) <= 0:
            self.add_text(self.player.x - 4, self.player.y - 12, 'LASER DODGED!', C14)
            self._laser_dodge_text_cd = 34
        self.add_shake(8, 2)
        sfx(1, 4)
        return
    return _old_player_hit_v18(self, dmg)
Game.player_hit = _player_hit_v18
def _v18_apply_empire_boss_bonus(game):
    _ensure_v14_state(game)
    if not _v17_empire_active(game):
        return
    boss_alive = any(isinstance(e, BossEnemy) and getattr(e, 'alive', True) for e in getattr(game, 'enemies', []))
    if not boss_alive:
        return
    if not getattr(game, 'empire_boss_tip_shown', False):
        game.empire_boss_tip_shown = True
        game.empire_boss_tip_timer = 0
        game.empire_boss_tip_pause = True
        game.empire_reach_boss_bonus_claimed = True
        game.player.max_hp = max(getattr(game.player, 'max_hp', 1), 2)
        game.player.hp = max(getattr(game.player, 'hp', 1), 2)
        game.player.invuln = max(getattr(game.player, 'invuln', 0), FPS * 30)
        game.add_text(game.player.x + 12, 36, 'QUEST COMPLETE: REACH BOSS  +1 HP', C10)
        game.add_text(game.player.x + 12, 50, 'DASH BEFORE LASER FIRES!', C14)
    elif getattr(game, 'empire_reach_boss_bonus_claimed', False) and getattr(game.player, 'max_hp', 1) < 2:
        game.player.max_hp = 2
        game.player.hp = max(getattr(game.player, 'hp', 1), 1)
globals()['_v17_apply_empire_boss_bonus'] = _v18_apply_empire_boss_bonus
_old_game_update_v18_modal = Game.update
def _game_update_v18_modal(self):
    globals()['_active_secret_game'] = self
    _ensure_v14_state(self)
    if getattr(self, '_laser_dodge_text_cd', 0) > 0:
        self._laser_dodge_text_cd -= 1
    if getattr(self, 'state', '') == 'playing' and getattr(self, 'empire_boss_tip_pause', False):
        self.player.invuln = max(getattr(self.player, 'invuln', 0), FPS * 30)
        if pyxel.btnp(pyxel.KEY_RETURN):
            self.empire_boss_tip_pause = False
            self.empire_boss_tip_resume_flash = FPS * 2
            self.player.invuln = max(getattr(self.player, 'invuln', 0), 100)
            self.add_text(self.player.x + 4, self.player.y - 18, 'SPAWN SHIELD', C12)
            sfx(1, 4)
        return
    return _old_game_update_v18_modal(self)
Game.update = _game_update_v18_modal
_old_draw_world_v18_modal = Game.draw_world
def _draw_world_v18_modal(self):
    _old_draw_world_v18_modal(self)
    _ensure_v14_state(self)
    pyxel.camera(0, 0)
    if getattr(self, 'empire_boss_tip_pause', False):
        pyxel.rect(18, 44, 284, 88, C0)
        pyxel.rectb(18, 44, 284, 88, C10)
        pyxel.rectb(21, 47, 278, 82, C14)
        pyxel.text(48, 56, 'QUEST COMPLETE: REACH BOSS FIGHT', C10)
        pyxel.text(59, 70, '+1 HEALTH FOR THIS BOSS', C7)
        pyxel.text(37, 86, 'LASER TIP: DASH SHORTLY BEFORE IT FIRES', C14)
        pyxel.text(45, 99, 'IF TIMED RIGHT, THE LASER MISSES YOU', C7)
        pyxel.text(82, 116, 'PRESS ENTER TO RESUME', C10 if (pyxel.frame_count // 8) % 2 == 0 else C7)
    elif getattr(self, 'empire_boss_tip_resume_flash', 0) > 0:
        self.empire_boss_tip_resume_flash -= 1
        if (pyxel.frame_count // 4) % 2 == 0:
            pyxel.text(95, 38, 'SPAWN SHIELD ACTIVE', C12)
Game.draw_world = _draw_world_v18_modal
def _draw_story_update_v18_exact_normal(self):
    pyxel.cls(C0)
    key = self.current_country()["key"]
    draw_country_outline(key, 92, 22, scale=4, color=C7, fill=False)
    draw_flag(key, 146, 44, 26, 16, pyxel.frame_count * 0.15)
    for i in range(0, SCREEN_W, 20):
        h = 18 + (i * 5) % 30
        pyxel.rect(i, 132 - h, 12, h, C5)
    for b in self.intro_bombs:
        pyxel.circ(int(b["x"]), int(b["y"]), 3, C8)
    for e in self.effects:
        e.draw(0)
    for p in self.particles:
        p.draw(0)
    lines = self.story_lines_progress()
    pyxel.text(40, 142, lines[0], C14)
    pyxel.text(40, 154, lines[1], C7)
    pyxel.text(40, 166, lines[2], C7)
    if self.intro_timer > 180:
        pyxel.text(104, 186, "PRESS ENTER", C7)
Game.draw_story_update = _draw_story_update_v18_exact_normal
def _draw_empire_ascension_v18(self):
    pyxel.cls(C0)
    _ensure_empire_state(self)
    t = getattr(self, 'empire_anim_timer', getattr(self, 'empire_ascension_timer', getattr(self, 'intro_timer', 0)))
    p = clamp(t / 520.0, 0, 1)
    key = self.current_country()['key']
    name = REAL_EMPIRE_NAMES.get(key, self.current_country()['name'] + ' EMPIRE') if 'REAL_EMPIRE_NAMES' in globals() else self.current_country()['name'] + ' EMPIRE'
    for i in range(0, SCREEN_W, 8):
        y = (i * 7 + t * 3) % SCREEN_H
        pyxel.pset(i, y, C1)
        if i % 24 == 0:
            pyxel.line(i, max(0, y - 8), i, y, C5)
    cx, cy = 160, 102
    for i in range(22):
        a = i * math.tau / 22 + t * 0.025
        r1 = 32 + int(12 * math.sin(t * 0.04 + i))
        r2 = 118 + int(20 * p)
        x1 = cx + int(math.cos(a) * r1)
        y1 = cy + int(math.sin(a) * r1)
        x2 = cx + int(math.cos(a) * r2)
        y2 = cy + int(math.sin(a) * r2)
        pyxel.line(x1, y1, x2, y2, C1 if i % 2 else C5)
    pyxel.rect(7, 7, 306, 186, C0)
    pyxel.rectb(7, 7, 306, 186, C10 if (t // 7) % 2 else C14)
    pyxel.rectb(11, 11, 298, 178, C7)
    pyxel.text(58, 18, '>>> EMPIRE ASCENSION SEQUENCE <<<', C14)
    pyxel.text(74, 31, name[:29], C10)
    try:
        draw_country_outline(key, 28, 50, scale=2, color=C14 if t % 20 < 10 else C7, fill=False)
    except Exception:
        pass
    pyxel.rect(215, 48, 66, 48, C1)
    pyxel.rectb(215, 48, 66, 48, C10)
    try:
        _draw_empire_flag(key, 224, 58, 48, 30, t * .20, True)
    except Exception:
        draw_flag(key, 224, 58, 48, 30, t * .20)
    tube_x, tube_y = 115, 50
    pyxel.rect(tube_x, tube_y, 90, 92, C1)
    pyxel.rectb(tube_x, tube_y, 90, 92, C7)
    for yy in range(tube_y + 4, tube_y + 88, 9):
        offset = int(math.sin((t + yy) * .07) * 8)
        pyxel.line(tube_x + 5, yy, tube_x + 84, yy + offset, C5)
    for rr in range(20, 58, 9):
        if t > rr * 2:
            pyxel.circb(cx, 95, rr + (t // 5) % 4, C10 if rr % 2 else C14)
    try:
        _draw_skin_preview(cx - 8, 96, self.skin_index, key, empire=t > 120)
    except Exception:
        self.player.draw(self.cam_x)
    upgrades = [
        ('SPEED BOOST', C12, min(1, p * 1.12)),
        ('JUMP CORE', C10, min(1, max(0, p - .08) * 1.20)),
        ('EMPIRE BULLETS', C14, min(1, max(0, p - .18) * 1.32)),
        ('BOSS QUEST HP', C7, min(1, max(0, p - .30) * 1.55)),
    ]
    for idx, (label, col, val) in enumerate(upgrades):
        y = 150 + idx * 10
        pyxel.text(28, y, label, C7)
        pyxel.rect(120, y, 116, 6, C1)
        pyxel.rectb(120, y, 116, 6, C7)
        pyxel.rect(121, y + 1, int(114 * val), 4, col)
        if val >= .99:
            pyxel.text(242, y, 'OK', C10)
    if '_draw_mini_bullet_thing_v16' in globals() and '_v16_bullet_style_for_key' in globals():
        styles = [_v16_bullet_style_for_key(key)]
        if key == 'italy':
            styles = ['laurel_spear', 'pila', 'wolf_spirit', 'gladiator_net']
        for i in range(11):
            a = t * .08 + i * .73
            bx = cx + int(math.cos(a) * (47 + i % 3 * 5))
            by = 96 + int(math.sin(a) * (25 + i % 2 * 8))
            _draw_mini_bullet_thing_v16(styles[i % len(styles)], bx, by, t + i * 17)
    if 110 < t < 160 and (t // 4) % 2 == 0:
        pyxel.text(94, 43, 'POWER SURGE!', C10)
    if 250 < t < 310:
        pyxel.text(78, 43, 'CUSTOM ARSENAL FORGED', C14)
    if t > 390:
        pyxel.text(82, 184, 'PRESS ENTER TO DEPLOY', C14 if (t // 10) % 2 else C7)
Game.draw_empire_ascension = _draw_empire_ascension_v18
_old_reset_run_v18 = Game.reset_run
def _reset_run_v18(self):
    result = _old_reset_run_v18(self)
    self.empire_boss_tip_pause = False
    self.empire_boss_tip_resume_flash = 0
    self._laser_dodge_text_cd = 0
    return result
Game.reset_run = _reset_run_v18
_old_next_level_v18 = Game.next_level
def _next_level_v18(self):
    result = _old_next_level_v18(self)
    self.empire_boss_tip_pause = False
    self.empire_boss_tip_resume_flash = 0
    self._laser_dodge_text_cd = 0
    return result
Game.next_level = _next_level_v18
FINAL_BUILD_TAG = 'v19-story-quake-map-km-multiplayer-tournament'
def _story_lines_intro_v19(self):
    name = self.current_country()["name"]
    key = self.current_country()["key"]
    empire = bool(getattr(self, 'empire_mode', False) or int(getattr(self, 'level', 1)) > CAMPAIGN_LEVELS)
    if empire:
        empire_name = REAL_EMPIRE_NAMES.get(key, name + ' EMPIRE') if 'REAL_EMPIRE_NAMES' in globals() else name + ' EMPIRE'
        return [
            empire_name + ' rises from the bunker network.',
            'Old borders are gone. Only the new banner moves.',
            'Enemy scanners lock on to your custom arsenal.',
            'Advance carefully: every kilometre is hostile ground.',
        ]
    openers = {
        'usa': ['The last coast broadcast goes silent.', 'Eagle drones circle the broken highways.', 'Command orders one final push inland.', 'Reach the bunker before the skyline burns.'],
        'china': ['Red storm clouds cover the province roads.', 'Dragon convoys vanish behind the mountains.', 'Your signal cuts through the static.', 'Break the siege and secure the next bunker.'],
        'germany': ['Yellow fog rolls over the ruined checkpoints.', 'Gas sirens echo between concrete towers.', 'Your squad marks a narrow safe route.', 'Move fast before the wind changes.'],
        'italy': ['Old marble arches crack under shellfire.', 'Legion banners flicker on emergency screens.', 'A bunker opens beyond the trenches.', 'Cross the ruins and keep the standard alive.'],
        'japan': ['Warning lights pulse through the neon rain.', 'Silent aircraft cut across the moon.', 'A mountain bunker answers your call.', 'Dash through the storm before the lasers sweep.'],
        'france': ['The capital radio plays one last anthem.', 'Supply balloons drift over shattered boulevards.', 'Resistance maps mark a hidden cellar gate.', 'Fight through and reclaim the route.'],
        'russia': ['Snow static buries the northern front.', 'Red symbols flash on abandoned command posts.', 'The earth itself shakes under artillery.', 'Push to the bunker before the whiteout closes.'],
        'brazil': ['Jungle alarms scream from the river bunkers.', 'Piranha shadows move under flooded streets.', 'A runway beacon blinks through the canopy.', 'Survive the swarm and reach extraction.'],
        'turkiye': ['Fortress lights burn across the strait.', 'Curved blades glint in the smoke.', 'A tunnel bunker opens under old stone.', 'Cut through the siege before dawn.'],
        'uk': ['Royal sirens ring over the blacked-out city.', 'Cannon smoke rolls down narrow streets.', 'A command bunker waits beyond the bridge.', 'Hold the crown line and advance.'],
    }
    return openers.get(key, [
        name + ' sends a final encrypted order.',
        'Enemy formations break through the outer line.',
        'Only the bunker road is still open.',
        'Run, fight, and keep the command alive.',
    ])
Game.story_lines_intro = _story_lines_intro_v19
def _story_lines_progress_v19(self):
    name = self.current_country()["name"]
    key = self.current_country()["key"]
    lvl = int(getattr(self, 'level', 1))
    empire = bool(getattr(self, 'empire_mode', False) or lvl > CAMPAIGN_LEVELS)
    if empire:
        arcs = {
            'usa': ['Eagle protocol is online.', 'Sky talons mark enemy armour.', 'The empire road opens west.', 'One more bunker falls silent.'],
            'china': ['Dragon rounds wake in the chamber.', 'Red-gold fire bends around the hills.', 'The empire line cuts deeper.', 'A new province answers your flag.'],
            'germany': ['Gas shells hiss in perfect formation.', 'The yellow cloud breaks their advance.', 'Empire filters hold for now.', 'The next sector is yours.'],
            'italy': ['The armoury rotates ancient weapons.', 'Pila, laurel, nets, and wolves answer.', 'The legion marches through sparks.', 'Another fortress becomes a gate.'],
            'japan': ['Steel stars spin through the dark.', 'Laser timing becomes a duel.', 'The mountain path opens.', 'Your empire moves without a sound.'],
            'france': ['Baguette rounds arc over barricades.', 'The resistance laughs through the smoke.', 'A boulevard bunker unlocks.', 'The empire advances with style.'],
            'russia': ['Red symbol rounds cut the snow.', 'Frozen bunkers switch allegiance.', 'The northern front bends.', 'The next kilometre belongs to you.'],
            'brazil': ['Piranha swarms flash through the flood.', 'The canopy shakes with engines.', 'River bunkers change hands.', 'The empire bites forward.'],
            'turkiye': ['Scimitar trails carve the night.', 'The strait guns fall quiet.', 'Stone tunnels open ahead.', 'The empire crosses another line.'],
            'uk': ['Royal cannonballs crush the barricade.', 'Bridge lights return one by one.', 'The old command table redraws itself.', 'The empire keeps calm and fires again.'],
        }
        lines = arcs.get(key, ['Custom rounds burn through the front.', 'The empire signal grows stronger.', 'Bunkers fall into formation.', 'The next sector opens.'])
        return lines
    progress_sets = [
        ['First bunker secured.', 'Enemy command did not expect survivors.', 'New trenches are appearing ahead.', 'Keep moving before the planes return.'],
        ['Supply radio recovered.', 'The next route cuts through broken ground.', 'Drones are adapting to your weapon rhythm.', 'Use SHIFT if the current gun is wrong.'],
        ['Forward scouts report a boss signature.', 'Dash timing will matter more now.', 'The bunker doors are still responding.', 'Push through the fire line.'],
        ['The old map is useless now.', 'Earthquakes are rewriting the road.', 'Look for jumpable cracks, not safe roads.', 'The command signal is close.'],
        ['Final normal-sector bunker ahead.', 'Every enemy wants the upgrade core.', 'Hold your fire rhythm and save your dash.', name + ' is almost ready to ascend.'],
    ]
    return progress_sets[(lvl - 1) % len(progress_sets)]
Game.story_lines_progress = _story_lines_progress_v19
def _v19_campaign_like(game):
    try:
        return game.is_campaign() or (game.is_multiplayer() and game.mp_mode_names[game.mp_mode_index] == 'CAMPAIGN ROTATION')
    except Exception:
        return False
def _v19_level_len(game):
    return 1500 if (getattr(game, 'mode_names', [''])[getattr(game, 'mode_index', 0)] == 'MULTIPLAYER' and getattr(game, 'mp_mode_names', [''])[getattr(game, 'mp_mode_index', 0)] == 'CAMPAIGN ROTATION') else LEVEL_DISTANCE
def _v19_quake_state(game):
    if not hasattr(game, 'v19_quake_prepared_level'):
        game.v19_quake_prepared_level = None
    if not hasattr(game, 'v19_last_quake_level'):
        game.v19_last_quake_level = -3
    if not hasattr(game, 'v19_quake_level'):
        game.v19_quake_level = False
    if not hasattr(game, 'v19_quake_seed'):
        game.v19_quake_seed = 0
    if not hasattr(game, 'v19_quake_shaped'):
        game.v19_quake_shaped = False
    if not hasattr(game, 'v19_quake_warning_timer'):
        game.v19_quake_warning_timer = 0
    if not hasattr(game, 'v19_quake_active_timer'):
        game.v19_quake_active_timer = 0
    if not hasattr(game, 'v19_quake_impact_pending'):
        game.v19_quake_impact_pending = False
def _v19_prepare_quake_level(game):
    _v19_quake_state(game)
    if not _v19_campaign_like(game):
        game.v19_quake_level = False
        game.v19_quake_prepared_level = None
        return
    lvl = int(getattr(game, 'level', 1))
    if game.v19_quake_prepared_level == lvl:
        return
    game.v19_quake_prepared_level = lvl
    game.v19_quake_shaped = False
    game.v19_quake_active_timer = 0
    game.v19_quake_warning_timer = 0
    game.v19_quake_impact_pending = False
    since = lvl - int(getattr(game, 'v19_last_quake_level', -3))
    should = False
    if lvl > 1:
        if since >= 3:
            should = True
        elif since >= 2 and random.random() < 0.78:
            should = True
        elif since >= 1 and random.random() < 0.18:
            should = True
    if should:
        game.v19_quake_level = True
        game.v19_last_quake_level = lvl
        game.v19_quake_seed = random.randint(1000, 999999)
        game.v19_quake_warning_timer = FPS * 2
        game.v19_quake_impact_pending = True
        game.earthquake_warning_timer = 0
        game.earthquake_timer = 0
        game.quake_red_border_timer = 0
    else:
        game.v19_quake_level = False
def _v19_add_quake_trench(terrain, x, rng):
    terrain.craters.append({
        'x': x,
        'r': rng.randint(18, 27),
        'gap_r': rng.randint(8, 13),
        'depth': rng.randint(17, 31),
    })
    if len(terrain.craters) > 60:
        terrain.craters.pop(0)
def _v19_add_quake_uplift(terrain, x, rng):
    if not hasattr(terrain, 'uplifts'):
        terrain.uplifts = []
    terrain.uplifts.append({
        'x': x,
        'r': rng.randint(38, 86),
        'height': rng.randint(9, 25),
    })
    if len(terrain.uplifts) > 40:
        terrain.uplifts.pop(0)
def _v19_shape_whole_quake_map(game):
    _v19_quake_state(game)
    if not game.v19_quake_level or game.v19_quake_shaped:
        return
    rng = random.Random(int(getattr(game, 'v19_quake_seed', 0)) + int(getattr(game, 'level', 1)) * 991)
    start = int(getattr(game, 'checkpoint_x', 40)) + 170
    end = int(getattr(game, 'checkpoint_x', 40)) + _v19_level_len(game) - 150
    x = start + rng.randint(40, 140)
    last_trench = -9999
    while x < end:
        x += rng.randint(120, 250)
        if x >= end:
            break
        roll = rng.random()
        if roll < 0.58 and abs(x - last_trench) > 150:
            _v19_add_quake_trench(game.terrain, x, rng)
            last_trench = x
        elif roll < 0.88:
            _v19_add_quake_uplift(game.terrain, x, rng)
        else:
            _v19_add_quake_uplift(game.terrain, x - rng.randint(20, 42), rng)
            if abs(x + 50 - last_trench) > 165:
                _v19_add_quake_trench(game.terrain, x + rng.randint(45, 82), rng)
                last_trench = x
    game.v19_quake_shaped = True
    game.v19_quake_active_timer = FPS * 8
    try:
        game.add_shake(24, 9)
        game.spawn_explosion(game.player.x + 82, game.terrain.ground_y_at(game.player.x + 82, game.difficulty_value()), 18, 'normal')
    except Exception:
        pass
def _update_earthquake_v19(game):
    _v19_quake_state(game)
    if not _v19_campaign_like(game) or getattr(game, 'state', '') != 'playing':
        return
    _v19_prepare_quake_level(game)
    game.earthquake_timer = 0
    game.earthquake_warning_timer = 0
    game.quake_red_border_timer = 0
    if not game.v19_quake_level:
        return
    if getattr(game, 'v19_quake_warning_timer', 0) > 0 or getattr(game, 'v19_quake_active_timer', 0) > 0:
        game.field_order = None
        game.field_order_progress = 0
        game.field_order_cooldown = max(getattr(game, 'field_order_cooldown', 0), FPS * 5)
    if game.v19_quake_warning_timer > 0:
        game.v19_quake_warning_timer -= 1
        if pyxel.frame_count % 2 == 0:
            game.add_shake(2, random.randint(4, 8))
        if game.v19_quake_warning_timer <= 0 and game.v19_quake_impact_pending:
            game.v19_quake_impact_pending = False
            _v19_shape_whole_quake_map(game)
        return
    if game.v19_quake_active_timer > 0:
        game.v19_quake_active_timer -= 1
        game.add_shake(2, random.randint(5, 9))
        if game.v19_quake_active_timer % 45 == 0:
            tx = game.player.x + random.randint(80, 260)
            gy = game.terrain.ground_y_at(tx, game.difficulty_value())
            game.texts.append(FlashText(tx, gy - 26, '!', C8, 28))
        return
globals()['_update_earthquake_v14'] = _update_earthquake_v19
def _draw_earthquake_overlay_v19(game):
    _v19_quake_state(game)
    if not getattr(game, 'v19_quake_level', False):
        return
    pyxel.camera(0, 0)
    if getattr(game, 'v19_quake_warning_timer', 0) > 0:
        sec = max(1, (game.v19_quake_warning_timer + FPS - 1) // FPS)
        col = C8 if (pyxel.frame_count // 6) % 2 == 0 else C10
        pyxel.text(94, 12, 'SEISMIC WARNING ' + str(sec), col)
        pyxel.text(78, 22, 'MAP WILL CRACK: JUMP THE GAPS', C7)
    elif getattr(game, 'v19_quake_active_timer', 0) > 0:
        col = C8 if (pyxel.frame_count // 4) % 2 == 0 else C10
        pyxel.text(98, 12, 'EARTHQUAKE - KEEP MOVING', col)
globals()['_draw_earthquake_overlay_v14'] = _draw_earthquake_overlay_v19
_old_v19_red_border = globals().get('_draw_red_danger_border_v16')
def _draw_red_danger_border_v16(label, color=C8):
    lab = str(label).upper()
    if 'QUAKE' in lab or 'EARTHQUAKE' in lab:
        pyxel.camera(0, 0)
        pyxel.text(96, 12, lab[:30], C8)
        return
    if _old_v19_red_border:
        return _old_v19_red_border(label, color)
globals()['_draw_red_danger_border_v16'] = _draw_red_danger_border_v16
_old_maybe_spawn_enemies_v19 = Game.maybe_spawn_enemies
def _maybe_spawn_enemies_v19(self):
    _v19_quake_state(self)
    if getattr(self, 'v19_quake_warning_timer', 0) > 0 or getattr(self, 'v19_quake_active_timer', 0) > 0:
        if random.random() < 0.62:
            return
    return _old_maybe_spawn_enemies_v19(self)
Game.maybe_spawn_enemies = _maybe_spawn_enemies_v19
_old_reset_run_v19 = Game.reset_run
def _reset_run_v19(self):
    result = _old_reset_run_v19(self)
    self.v19_quake_prepared_level = None
    self.v19_last_quake_level = -2
    self.v19_quake_level = False
    self.v19_quake_shaped = False
    self.v19_quake_warning_timer = 0
    self.v19_quake_active_timer = 0
    self.v19_quake_impact_pending = False
    _v19_prepare_quake_level(self)
    return result
Game.reset_run = _reset_run_v19
_old_next_level_v19 = Game.next_level
def _next_level_v19(self):
    result = _old_next_level_v19(self)
    self.v19_quake_prepared_level = None
    _v19_prepare_quake_level(self)
    return result
Game.next_level = _next_level_v19
_old_respawn_v19_quake = Game.respawn_or_game_over
def _respawn_or_game_over_v19_quake(self):
    result = _old_respawn_v19_quake(self)
    if _v19_campaign_like(self) and getattr(self, 'v19_quake_level', False):
        self.v19_quake_shaped = False
        self.v19_quake_warning_timer = FPS * 2
        self.v19_quake_active_timer = 0
        self.v19_quake_impact_pending = True
    return result
Game.respawn_or_game_over = _respawn_or_game_over_v19_quake
def _v19_survival_metres(game):
    return max(0, int(max(getattr(game, 'total_distance', 0), getattr(game, 'distance', 0), getattr(game.player, 'x', 40) - 40)))
def _v19_survival_km_text(metres):
    return f"{metres / 1000.0:.2f}km"
def _draw_v12_tiny_progress_text(game):
    if not (game.is_campaign() or (game.is_multiplayer() and getattr(game, 'mp_mode_names', [''])[getattr(game, 'mp_mode_index', 0)] == 'CAMPAIGN ROTATION')):
        if game.is_survival() or (game.is_multiplayer() and getattr(game, 'mp_mode_names', [''])[getattr(game, 'mp_mode_index', 0)] == 'SURVIVAL ROTATION'):
            pyxel.text(6, 184, 'KM ' + _v19_survival_km_text(_v19_survival_metres(game)), C7)
        return
    level_len = 1500 if (game.is_multiplayer() and game.mp_mode_names[game.mp_mode_index] == 'CAMPAIGN ROTATION') else LEVEL_DISTANCE
    remaining = max(0, int(level_len - getattr(game, 'distance', 0)))
    if getattr(game, 'empire_mode', False):
        pyxel.text(6, 184, 'EMP ' + str(getattr(game, 'level', 1)) + '/' + str(EMPIRE_TOTAL_LEVELS) + '  ' + str(remaining) + 'm', C10)
    else:
        pyxel.text(6, 184, 'LV ' + str(getattr(game, 'level', 1)) + '/' + str(CAMPAIGN_LEVELS) + '  ' + str(remaining) + 'm', C7)
globals()['_draw_v12_tiny_progress_text'] = _draw_v12_tiny_progress_text
_old_draw_game_over_v19 = Game.draw_game_over
def _draw_game_over_v19(self):
    if self.is_survival():
        pyxel.cls(C0)
        metres = _v19_survival_metres(self)
        best = max(int(getattr(self, 'best_survival', 0)), metres)
        pyxel.text(120, 64, 'GAME OVER', C8)
        draw_flag(self.current_country()['key'], 146, 78, 26, 16, pyxel.frame_count * 0.1)
        pyxel.text(92, 104, 'DISTANCE ' + _v19_survival_km_text(metres), C7)
        pyxel.text(104, 118, 'BEST ' + _v19_survival_km_text(best), C10)
        pyxel.text(102, 132, 'SCORE ' + str(self.player.score), C14)
        pyxel.text(98, 152, 'PRESS ENTER FOR MENU', C7)
        return
    return _old_draw_game_over_v19(self)
Game.draw_game_over = _draw_game_over_v19
_old_respawn_v19_survival = Game.respawn_or_game_over
def _respawn_or_game_over_v19_survival(self):
    if self.is_survival():
        self.spawn_explosion(self.player.x + 8, self.player.y + 10, 18, 'normal')
        self.add_shake(12, 4)
        metres = _v19_survival_metres(self)
        self.best_survival = max(int(getattr(self, 'best_survival', 0)), metres)
        self.save_data['best_survival'] = self.best_survival
        self.save_data['coins_total'] = self.coins_total
        self.save_data['unlocked_skins'] = self.unlocked_skins
        self.save_data['selected_country'] = self.country_index
        self.save_data['extra_hp_bonus'] = self.extra_hp_bonus
        save_save(self.save_data)
        self.state = 'game_over'
        play_story_music()
        return
    return _old_respawn_v19_survival(self)
Game.respawn_or_game_over = _respawn_or_game_over_v19_survival
_old_v19_bullet_style_for_key = globals().get('_v16_bullet_style_for_key')
def _v16_bullet_style_for_key(key):
    if key == 'russia':
        return 'communism_symbol'
    if _old_v19_bullet_style_for_key:
        return _old_v19_bullet_style_for_key(key)
    return 'empire_flag_bolt'
globals()['_v16_bullet_style_for_key'] = _v16_bullet_style_for_key
_old_empire_bullet_style_v19 = globals().get('_empire_bullet_style_v15')
def _empire_bullet_style_v15(key):
    if key == 'russia':
        return 'communism_symbol'
    if _old_empire_bullet_style_v19:
        return _old_empire_bullet_style_v19(key)
    return 'empire_flag_bolt'
globals()['_empire_bullet_style_v15'] = _empire_bullet_style_v15
_old_bullet_draw_v19_symbol = Bullet.draw
def _bullet_draw_v19_symbol(self, cam_x):
    if getattr(self, 'empire_style', None) != 'communism_symbol':
        return _old_bullet_draw_v19_symbol(self, cam_x)
    sx, sy = int(self.x - cam_x), int(self.y)
    if sx < -35 or sx > SCREEN_W + 35 or sy < -35 or sy > SCREEN_H + 35:
        return
    seed = getattr(self, 'empire_seed', 0)
    spin = ((pyxel.frame_count + seed) // 5) % 2
    pyxel.circ(sx, sy, 7, C8)
    pyxel.circb(sx, sy, 8, C10)
    if spin:
        pyxel.line(sx - 4, sy + 3, sx + 4, sy - 5, C10)
        pyxel.line(sx - 1, sy - 5, sx + 5, sy - 5, C10)
        pyxel.circb(sx + 1, sy + 1, 5, C10)
        pyxel.circ(sx - 1, sy - 1, 5, C8)
        pyxel.line(sx + 2, sy + 3, sx + 6, sy + 6, C10)
    else:
        pyxel.line(sx - 5, sy - 3, sx + 3, sy + 5, C10)
        pyxel.line(sx - 5, sy - 3, sx - 5, sy + 2, C10)
        pyxel.circb(sx, sy, 5, C10)
        pyxel.circ(sx - 2, sy - 1, 5, C8)
        pyxel.line(sx + 1, sy + 3, sx + 5, sy + 6, C10)
    pyxel.pset(sx, sy, C7)
Bullet.draw = _bullet_draw_v19_symbol
_old_draw_mini_v19 = globals().get('_draw_mini_bullet_thing_v16')
def _draw_mini_bullet_thing_v16(style, x, y, seed):
    if style == 'communism_symbol':
        pyxel.circ(x, y, 5, C8)
        pyxel.circb(x, y, 6, C10)
        pyxel.line(x - 3, y + 2, x + 3, y - 4, C10)
        pyxel.line(x - 1, y - 4, x + 4, y - 4, C10)
        pyxel.circb(x + 1, y + 1, 4, C10)
        pyxel.circ(x - 1, y, 4, C8)
        return
    if _old_draw_mini_v19:
        return _old_draw_mini_v19(style, x, y, seed)
globals()['_draw_mini_bullet_thing_v16'] = _draw_mini_bullet_thing_v16
_MP19_COLORS = [C12, C10, C14]
_MP19_KEYS = [
    {'left': pyxel.KEY_A, 'right': pyxel.KEY_D, 'jump': pyxel.KEY_W, 'down': pyxel.KEY_S, 'shoot': pyxel.KEY_C, 'label': 'P1  WASD + C SHOOT'},
    {'left': pyxel.KEY_LEFT, 'right': pyxel.KEY_RIGHT, 'jump': pyxel.KEY_UP, 'down': pyxel.KEY_DOWN, 'shoot': pyxel.KEY_SHIFT, 'label': 'P2  ARROWS + SHIFT SHOOT'},
    {'left': pyxel.KEY_H, 'right': pyxel.KEY_K, 'jump': pyxel.KEY_U, 'down': pyxel.KEY_J, 'shoot': pyxel.KEY_B, 'label': 'P3  U/H/J/K + B SHOOT'},
]
def _mp19_setup_select(self):
    self.mp_player_count = int(clamp(getattr(self, 'mp_player_count', 2), 2, 3))
    self.state = 'mp_select_count'
    self.mp_round = 1
    self.mp_empire_players = set()
    self.mp_match_winner = None
    self.mp_round_winner = None
    self.mp_round_message = ''
    self.mp_scores = [0 for _ in range(3)]
    stop_story_music()
_old_update_menu_v19 = Game.update_menu
def _update_menu_v19(self):
    if pyxel.btnp(pyxel.KEY_RETURN) and self.is_multiplayer() and self.skin_index in self.unlocked_skins:
        self.player.skin_index = self.skin_index
        self.player.country_index = self.country_index
        _mp19_setup_select(self)
        return
    return _old_update_menu_v19(self)
Game.update_menu = _update_menu_v19
def _mp19_start_round(game):
    count = int(clamp(getattr(game, 'mp_player_count', 2), 2, 3))
    game.terrain.reset()
    game.mp_arena_players = []
    game.mp_arena_bullets = []
    game.mp_arena_hazards = []
    game.mp_arena_spawn_cd = 45
    game.mp_arena_timer = 0
    game.cam_x = 0
    game.shake_timer = 0
    game.effects.clear(); game.particles.clear(); game.texts.clear()
    for i in range(count):
        x = 58 + i * 28
        y = game.terrain.ground_y_at(x + 8, game.difficulty_value()) - PLAYER_H
        empire = i in getattr(game, 'mp_empire_players', set())
        game.mp_arena_players.append({
            'idx': i, 'x': x, 'y': y, 'vx': 0.0, 'vy': 0.0, 'facing': 1,
            'hp': 3, 'alive': True, 'dist': 0, 'invuln': 90, 'shot_cd': 0,
            'empire': empire, 'on_ground': True, 'dead_timer': 0,
        })
    game.mp_scores = [0 for _ in range(count)]
    game.state = 'mp_arena'
    stop_story_music()
def _mp19_player_hit(game, p, dmg=1):
    if not p.get('alive') or p.get('invuln', 0) > 0:
        return
    p['hp'] -= dmg
    p['invuln'] = 60
    game.spawn_explosion(p['x'] + 8, p['y'] + 10, 10, 'normal')
    game.add_shake(5, 3)
    if p['hp'] <= 0:
        p['alive'] = False
        p['dead_timer'] = 45
        game.spawn_explosion(p['x'] + 8, p['y'] + 10, 20, 'normal')
def _mp19_update_player(game, p):
    keys = _MP19_KEYS[p['idx']]
    if not p.get('alive'):
        p['dead_timer'] = max(0, p.get('dead_timer', 0) - 1)
        return
    if p['invuln'] > 0:
        p['invuln'] -= 1
    if p['shot_cd'] > 0:
        p['shot_cd'] -= 1
    speed = 1.85 + (0.26 if p.get('empire') else 0.0)
    jump = -5.35 - (0.25 if p.get('empire') else 0.0)
    vx = 0
    if pyxel.btn(keys['left']):
        vx -= speed; p['facing'] = -1
    if pyxel.btn(keys['right']):
        vx += speed; p['facing'] = 1
    p['vx'] = vx
    if pyxel.btnp(keys['jump']) and p.get('on_ground'):
        p['vy'] = jump
        p['on_ground'] = False
    if pyxel.btn(keys['down']) and not p.get('on_ground'):
        p['vy'] += 0.35
    p['vy'] = min(p['vy'] + GRAVITY, MAX_FALL)
    p['x'] += p['vx']
    p['y'] += p['vy']
    p['x'] = max(0, p['x'])
    if p['x'] < game.cam_x - 30:
        _mp19_player_hit(game, p, 3)
    foot = p['x'] + 8
    gap = game.terrain.is_gap_at(foot, game.difficulty_value())
    gy = game.terrain.ground_y_at(foot, game.difficulty_value())
    if not gap and p['y'] >= gy - PLAYER_H:
        p['y'] = gy - PLAYER_H
        p['vy'] = 0
        p['on_ground'] = True
    else:
        p['on_ground'] = False
    if p['y'] > SCREEN_H + 40:
        _mp19_player_hit(game, p, 3)
    p['dist'] += max(0.65, max(0, p['vx']) + 0.45)
    if pyxel.btnp(keys['shoot']) and p['shot_cd'] <= 0:
        p['shot_cd'] = 17 if not p.get('empire') else 13
        style = None
        if p.get('empire'):
            style = _v16_bullet_style_for_key(game.current_country()['key'])
            if game.current_country()['key'] == 'italy':
                style = random.choice(_ITALY_BULLETS_V15) if '_ITALY_BULLETS_V15' in globals() else 'laurel_spear'
        game.mp_arena_bullets.append({
            'x': p['x'] + 8, 'y': p['y'] + 11, 'vx': p['facing'] * (6.0 if not p.get('empire') else 6.4),
            'vy': 0, 'owner': p['idx'], 'life': 80, 'style': style, 'seed': random.randint(0,9999)
        })
        sfx(0, 0)
def _mp19_update_bullets_and_hazards(game):
    for b in game.mp_arena_bullets:
        b['x'] += b['vx']; b['y'] += b.get('vy', 0); b['life'] -= 1
    game.mp_arena_spawn_cd -= 1
    alive_count = sum(1 for p in game.mp_arena_players if p.get('alive'))
    if game.mp_arena_spawn_cd <= 0:
        game.mp_arena_spawn_cd = max(22, 54 - game.mp_arena_timer // 360)
        if random.random() < 0.72:
            game.mp_arena_hazards.append({'kind': 'missile', 'x': game.cam_x + random.randint(40, SCREEN_W + 170), 'y': -16, 'vy': random.uniform(2.0, 3.5), 'alive': True})
        else:
            mx = game.cam_x + random.randint(80, SCREEN_W + 180)
            gy = game.terrain.ground_y_at(mx, game.difficulty_value())
            game.mp_arena_hazards.append({'kind': 'mine', 'x': mx, 'y': gy - 5, 'timer': random.randint(90, 180), 'alive': True})
        if alive_count > 1 and random.random() < 0.22:
            tx = game.cam_x + SCREEN_W + random.randint(80, 260)
            game.terrain.craters.append({'x': tx, 'r': random.randint(18, 25), 'gap_r': random.randint(8, 12), 'depth': random.randint(16, 26)})
    for h in game.mp_arena_hazards:
        if not h.get('alive'):
            continue
        if h['kind'] == 'missile':
            h['y'] += h['vy']
            gy = game.terrain.ground_y_at(h['x'], game.difficulty_value())
            if h['y'] >= gy:
                h['alive'] = False
                game.spawn_explosion(h['x'], gy, 14, 'normal')
                game.add_shake(4, 3)
        elif h['kind'] == 'mine':
            h['timer'] -= 1
            if h['timer'] <= 0:
                h['alive'] = False
                game.spawn_explosion(h['x'], h['y'], 16, 'normal')
    for b in game.mp_arena_bullets:
        if b['life'] <= 0:
            continue
        for h in game.mp_arena_hazards:
            if h.get('alive') and abs(b['x'] - h['x']) < 12 and abs(b['y'] - h['y']) < 14:
                h['alive'] = False; b['life'] = 0
                game.spawn_explosion(h['x'], h['y'], 12, 'normal')
                break
    for h in game.mp_arena_hazards:
        if not h.get('alive'):
            continue
        for p in game.mp_arena_players:
            if p.get('alive') and rects_overlap(p['x'] + 3, p['y'] + 3, 10, 18, h['x'] - 5, h['y'] - 5, 10, 10):
                h['alive'] = False
                _mp19_player_hit(game, p, 1)
                break
    game.mp_arena_bullets = [b for b in game.mp_arena_bullets if b['life'] > 0 and game.cam_x - 80 < b['x'] < game.cam_x + SCREEN_W + 160]
    game.mp_arena_hazards = [h for h in game.mp_arena_hazards if h.get('alive') and game.cam_x - 80 < h['x'] < game.cam_x + SCREEN_W + 220 and h.get('y', 0) < SCREEN_H + 40]
def _mp19_finish_round(game):
    players = getattr(game, 'mp_arena_players', [])
    if not players:
        return
    winner = max(players, key=lambda p: p.get('dist', 0))['idx']
    game.mp_round_winner = winner
    game.mp_scores = [int(p.get('dist', 0)) for p in players]
    already_empire = winner in getattr(game, 'mp_empire_players', set())
    if getattr(game, 'mp_round', 1) == 1:
        game.mp_empire_players.add(winner)
        game.mp_round_message = 'P' + str(winner + 1) + ' SURVIVED LONGEST - EMPIRE UPGRADE'
        game.state = 'mp_round_result'
    elif getattr(game, 'mp_round', 1) == 2:
        if already_empire:
            game.mp_match_winner = winner
            game.state = 'mp_final'
        else:
            game.mp_empire_players.add(winner)
            game.mp_round_message = 'P' + str(winner + 1) + ' ALSO ASCENDS - FINAL ROUND'
            game.state = 'mp_round_result'
    else:
        game.mp_match_winner = winner
        game.state = 'mp_final'
    play_story_music()
def _update_mp_select_count_v19(self):
    if pyxel.btnp(pyxel.KEY_LEFT) or pyxel.btnp(pyxel.KEY_A):
        self.mp_player_count = max(2, int(getattr(self, 'mp_player_count', 2)) - 1)
    if pyxel.btnp(pyxel.KEY_RIGHT) or pyxel.btnp(pyxel.KEY_D):
        self.mp_player_count = min(3, int(getattr(self, 'mp_player_count', 2)) + 1)
    if pyxel.btnp(pyxel.KEY_RETURN):
        self.state = 'mp_tutorial'
    if pyxel.btnp(pyxel.KEY_ESCAPE):
        self.state = 'menu'
def _update_mp_tutorial_v19(self):
    if pyxel.btnp(pyxel.KEY_RETURN):
        self.mp_round = 1
        self.mp_empire_players = set()
        _mp19_start_round(self)
    if pyxel.btnp(pyxel.KEY_ESCAPE):
        self.state = 'menu'
def _update_mp_arena_v19(self):
    self.mp_arena_timer = getattr(self, 'mp_arena_timer', 0) + 1
    for p in list(getattr(self, 'mp_arena_players', [])):
        _mp19_update_player(self, p)
    live = [p for p in self.mp_arena_players if p.get('alive')]
    if live:
        leader_x = max(p['x'] for p in live)
        self.cam_x = max(0, leader_x - 116)
    _mp19_update_bullets_and_hazards(self)
    for e in self.effects: e.update()
    for pa in self.particles: pa.update()
    self.effects = [e for e in self.effects if e.alive]
    self.particles = [p for p in self.particles if p.alive]
    self.update_shake()
    live_count = sum(1 for p in self.mp_arena_players if p.get('alive'))
    if live_count <= 1 and self.mp_arena_timer > FPS * 4:
        _mp19_finish_round(self)
    elif self.mp_arena_timer > FPS * 90:
        _mp19_finish_round(self)
def _update_mp_round_result_v19(self):
    if pyxel.btnp(pyxel.KEY_RETURN):
        self.mp_round += 1
        _mp19_start_round(self)
    if pyxel.btnp(pyxel.KEY_ESCAPE):
        self.state = 'menu'
        stop_story_music()
def _update_mp_final_v19(self):
    if pyxel.btnp(pyxel.KEY_RETURN):
        self.state = 'menu'
        stop_story_music()
_old_update_v19 = Game.update
def _update_v19(self):
    st = getattr(self, 'state', '')
    if st == 'mp_select_count':
        _update_mp_select_count_v19(self); return
    if st == 'mp_tutorial':
        _update_mp_tutorial_v19(self); return
    if st == 'mp_arena':
        _update_mp_arena_v19(self); return
    if st == 'mp_round_result':
        _update_mp_round_result_v19(self); return
    if st == 'mp_final':
        _update_mp_final_v19(self); return
    return _old_update_v19(self)
Game.update = _update_v19
def _draw_mp_select_count_v19(self):
    self.draw_background()
    pyxel.rect(36, 30, 248, 136, C0)
    pyxel.rectb(36, 30, 248, 136, C12)
    pyxel.text(90, 48, 'MULTIPLAYER SURVIVAL', C14)
    pyxel.text(68, 72, 'CHOOSE PLAYER NUMBER', C7)
    for n in (2, 3):
        x = 112 + (n - 2) * 58
        selected = int(getattr(self, 'mp_player_count', 2)) == n
        pyxel.rect(x, 96, 42, 28, C12 if selected else C0)
        pyxel.rectb(x, 96, 42, 28, C7)
        pyxel.text(x + 17, 107, str(n), C0 if selected else C7)
    pyxel.text(74, 142, 'LEFT/RIGHT SELECT   ENTER CONTINUE', C7)
def _draw_mp_tutorial_v19(self):
    pyxel.cls(C0)
    pyxel.rect(20, 18, 280, 164, C0)
    pyxel.rectb(20, 18, 280, 164, C7)
    pyxel.text(92, 32, 'MULTIPLAYER QUICK TRAINING', C14)
    lines = [
        'Goal: survive the farthest distance in kilometres.',
        _MP19_KEYS[0]['label'],
        _MP19_KEYS[1]['label'],
    ]
    if int(getattr(self, 'mp_player_count', 2)) >= 3:
        lines.append(_MP19_KEYS[2]['label'])
    lines += [
        'Each player has 3 HP.',
        'Round 1 winner gets EMPIRE bullets + movement.',
        'If that empire player wins Round 2, they win.',
        'If someone else wins Round 2, they ascend too.',
        'Then Round 3 decides the final winner.',
    ]
    for i, line in enumerate(lines):
        pyxel.text(42, 54 + i * 12, line[:48], C10 if i in (1,2,3) else C7)
    pyxel.text(90, 170, 'PRESS ENTER TO START ROUND 1', C14 if (pyxel.frame_count // 12) % 2 == 0 else C7)
def _draw_mp_player_v19(p, cam_x, country_key):
    sx, sy = int(p['x'] - cam_x), int(p['y'])
    col = _MP19_COLORS[p['idx'] % len(_MP19_COLORS)]
    if p.get('invuln', 0) > 0 and (pyxel.frame_count // 4) % 2 == 0:
        pyxel.circb(sx + 8, sy + 10, 13, C7)
    if not p.get('alive'):
        pyxel.text(sx, sy + 8, 'KO', C8)
        return
    if p.get('empire'):
        pyxel.circb(sx + 8, sy + 11, 15, C10)
        if '_draw_mini_bullet_thing_v16' in globals():
            _draw_mini_bullet_thing_v16(_v16_bullet_style_for_key(country_key), sx + 8, sy - 7, pyxel.frame_count + p['idx'] * 17)
    pyxel.rect(sx + 3, sy + 5, 10, 15, col)
    pyxel.rect(sx + 4, sy, 8, 7, C7)
    pyxel.pset(sx + (11 if p.get('facing', 1) > 0 else 5), sy + 3, C0)
    pyxel.text(sx + 1, sy - 9, 'P' + str(p['idx'] + 1), C10 if p.get('empire') else C7)
def _draw_mp_arena_v19(self):
    self.draw_background()
    shake_x = getattr(self, 'shake_x', 0); shake_y = getattr(self, 'shake_y', 0)
    pyxel.camera(int(shake_x), int(shake_y))
    self.terrain.draw(self.cam_x, self.difficulty_value())
    for h in getattr(self, 'mp_arena_hazards', []):
        sx, sy = int(h['x'] - self.cam_x), int(h['y'])
        if h['kind'] == 'missile':
            pyxel.rect(sx - 3, sy - 9, 6, 14, C8); pyxel.pset(sx, sy - 10, C7); pyxel.line(sx - 2, sy + 5, sx, sy + 10, C10); pyxel.line(sx + 2, sy + 5, sx, sy + 10, C10)
        else:
            pyxel.circ(sx, sy, 5, C5); pyxel.circb(sx, sy, 5, C8); pyxel.text(sx - 2, sy - 12, '!', C8)
    country_key = self.current_country()['key']
    for b in getattr(self, 'mp_arena_bullets', []):
        sx, sy = int(b['x'] - self.cam_x), int(b['y'])
        style = b.get('style')
        if style and '_draw_mini_bullet_thing_v16' in globals():
            _draw_mini_bullet_thing_v16(style, sx, sy, b.get('seed', 0))
        else:
            pyxel.line(sx - int(sign(b['vx']) * 7), sy, sx, sy, C7)
            pyxel.circ(sx, sy, 2, C10)
    for p in getattr(self, 'mp_arena_players', []):
        _draw_mp_player_v19(p, self.cam_x, country_key)
    for e in self.effects: e.draw(self.cam_x)
    for pa in self.particles: pa.draw(self.cam_x)
    pyxel.camera(0, 0)
    pyxel.rect(0, 0, SCREEN_W, 24, C0); pyxel.rectb(0, 0, SCREEN_W, 24, C5)
    pyxel.text(6, 5, 'MP ROUND ' + str(getattr(self, 'mp_round', 1)), C14)
    for p in getattr(self, 'mp_arena_players', []):
        x = 82 + p['idx'] * 78
        txt = 'P' + str(p['idx'] + 1) + ' ' + _v19_survival_km_text(int(p.get('dist', 0)))
        pyxel.text(x, 5, txt, C10 if p.get('empire') else C7)
        pyxel.text(x, 15, 'HP ' + str(max(0, int(p.get('hp', 0)))) + (' EMP' if p.get('empire') else ''), _MP19_COLORS[p['idx'] % len(_MP19_COLORS)])
def _draw_mp_round_result_v19(self):
    pyxel.cls(C0)
    pyxel.rect(28, 28, 264, 144, C0); pyxel.rectb(28, 28, 264, 144, C10)
    pyxel.text(82, 44, 'ROUND ' + str(getattr(self, 'mp_round', 1)) + ' RESULT', C14)
    pyxel.text(54, 66, str(getattr(self, 'mp_round_message', 'ROUND COMPLETE'))[:42], C7)
    for i, score in enumerate(getattr(self, 'mp_scores', [])):
        emp = '  EMPIRE' if i in getattr(self, 'mp_empire_players', set()) else ''
        pyxel.text(72, 88 + i * 13, 'P' + str(i + 1) + '  ' + _v19_survival_km_text(int(score)) + emp, C10 if emp else C7)
    pyxel.text(76, 150, 'PRESS ENTER FOR NEXT ROUND', C14 if (pyxel.frame_count // 10) % 2 == 0 else C7)
def _draw_mp_final_v19(self):
    pyxel.cls(C0)
    winner = int(getattr(self, 'mp_match_winner', 0))
    pyxel.rect(36, 32, 248, 136, C0); pyxel.rectb(36, 32, 248, 136, C14)
    pyxel.text(98, 54, 'MULTIPLAYER WINNER', C10)
    pyxel.text(132, 78, 'P' + str(winner + 1), C14)
    pyxel.text(74, 102, 'Empire survival tournament complete.', C7)
    for i, score in enumerate(getattr(self, 'mp_scores', [])):
        pyxel.text(86, 122 + i * 10, 'P' + str(i + 1) + ': ' + _v19_survival_km_text(int(score)), C10 if i == winner else C7)
    pyxel.text(92, 154, 'PRESS ENTER FOR MENU', C7)
_old_draw_v19 = Game.draw
def _draw_v19(self):
    st = getattr(self, 'state', '')
    if st == 'mp_select_count':
        _draw_mp_select_count_v19(self); return
    if st == 'mp_tutorial':
        _draw_mp_tutorial_v19(self); return
    if st == 'mp_arena':
        _draw_mp_arena_v19(self); return
    if st == 'mp_round_result':
        _draw_mp_round_result_v19(self); return
    if st == 'mp_final':
        _draw_mp_final_v19(self); return
    return _old_draw_v19(self)
Game.draw = _draw_v19
FINAL_BUILD_TAG = 'v20-boss-only-quake-guarantee-chrono-story-stable-mp'
def _v20_is_multiplayer_context(game):
    try:
        if game.is_multiplayer():
            return True
    except Exception:
        pass
    return str(getattr(game, 'state', '')).startswith('mp_')
def _v20_boss_active(game):
    try:
        return any(isinstance(e, BossEnemy) and getattr(e, 'alive', True) for e in getattr(game, 'enemies', []))
    except Exception:
        return False
def _v20_clear_boss_clutter(game):
    if not _v20_boss_active(game):
        return
    try:
        game.enemies = [e for e in game.enemies if isinstance(e, BossEnemy)]
    except Exception:
        pass
    try:
        game.enemy_bullets.clear()
    except Exception:
        game.enemy_bullets = []
    try:
        game.landmines.clear()
    except Exception:
        game.landmines = []
    try:
        game.field_order = None
        game.field_order_progress = 0
        game.field_order_timer = 0
        game.field_order_cooldown = max(getattr(game, 'field_order_cooldown', 0), FPS * 8)
    except Exception:
        pass
def _boss_update_v20(self, game):
    self.wave += 0.035
    self.y = 64 + math.sin(self.wave) * 6
    self.x += self.dir * 0.45
    if self.x < game.player.x + 92:
        self.dir = 1
    if self.x > game.player.x + 205:
        self.dir = -1
    self.update_flash()
    if getattr(self, 'laser_hit_cd', 0) > 0:
        self.laser_hit_cd -= 1
    self.missile_cd = 999999
    if getattr(self, 'laser_timer', 0) > 0:
        self.laser_timer -= 1
        px, py, pw, ph = game.player.hurtbox()
        beam_y = self.laser_y
        if self.laser_hit_cd <= 0 and px < self.x + 36 and py < beam_y + 5 and py + ph > beam_y - 5:
            game.player_hit(1)
            self.laser_hit_cd = 35
        return
    if getattr(self, 'laser_charge', 0) > 0:
        self.laser_charge -= 1
        self.laser_y = game.player.y + 10
        if self.laser_charge <= 0:
            self.laser_timer = 22
            self.fire_cd = 105 if self.hp >= 28 else 78
            game.add_shake(6, 2)
            sfx(1, 7)
        return
    self.fire_cd -= 1
    if self.fire_cd <= 0:
        self.laser_charge = 42
        self.laser_y = game.player.y + 10
        self.fire_cd = 9999
        game.add_text(self.x - 10, self.y - 14, 'LASER LOCK', C8)
BossEnemy.update = _boss_update_v20
_old_maybe_spawn_enemies_v20 = Game.maybe_spawn_enemies
def _maybe_spawn_enemies_v20(self):
    if _v20_boss_active(self):
        _v20_clear_boss_clutter(self)
        return
    if _v20_is_multiplayer_context(self):
        return
    return _old_maybe_spawn_enemies_v20(self)
Game.maybe_spawn_enemies = _maybe_spawn_enemies_v20
_old_update_playing_v20 = Game.update_playing
def _update_playing_v20(self):
    if _v20_boss_active(self):
        _v20_clear_boss_clutter(self)
    result = _old_update_playing_v20(self)
    if _v20_boss_active(self):
        _v20_clear_boss_clutter(self)
    return result
Game.update_playing = _update_playing_v20
_old_update_field_order_v20 = globals().get('_update_field_order')
def _update_field_order(game):
    if (_v20_is_multiplayer_context(game) or _v20_boss_active(game)
            or getattr(game, 'v19_quake_warning_timer', 0) > 0
            or getattr(game, 'v19_quake_active_timer', 0) > 0):
        game.field_order = None
        game.field_order_progress = 0
        game.field_order_timer = 0
        game.field_order_cooldown = max(getattr(game, 'field_order_cooldown', 0), FPS * 6)
        return
    if _old_update_field_order_v20:
        return _old_update_field_order_v20(game)
globals()['_update_field_order'] = _update_field_order
_old_draw_v12_radio_mission_v20 = globals().get('_draw_v12_radio_mission')
def _draw_v12_radio_mission(game):
    if (_v20_is_multiplayer_context(game) or _v20_boss_active(game)
            or getattr(game, 'v19_quake_warning_timer', 0) > 0
            or getattr(game, 'v19_quake_active_timer', 0) > 0):
        return
    if _old_draw_v12_radio_mission_v20:
        return _old_draw_v12_radio_mission_v20(game)
globals()['_draw_v12_radio_mission'] = _draw_v12_radio_mission
def _v19_campaign_like(game):
    try:
        return (not _v20_is_multiplayer_context(game)) and game.is_campaign()
    except Exception:
        return False
globals()['_v19_campaign_like'] = _v19_campaign_like
def _v20_quake_state(game):
    _v19_quake_state(game)
    if not hasattr(game, 'v20_survival_quake_next_x'):
        game.v20_survival_quake_next_x = 1250 + random.randint(0, 500)
    if not hasattr(game, 'v20_survival_quake_serial'):
        game.v20_survival_quake_serial = 0
def _v19_prepare_quake_level(game):
    _v20_quake_state(game)
    if not _v19_campaign_like(game):
        game.v19_quake_level = False
        game.v19_quake_prepared_level = None
        return
    lvl = int(getattr(game, 'level', 1))
    if game.v19_quake_prepared_level == lvl:
        return
    game.v19_quake_prepared_level = lvl
    game.v19_quake_shaped = False
    game.v19_quake_active_timer = 0
    game.v19_quake_warning_timer = 0
    game.v19_quake_impact_pending = False
    since = lvl - int(getattr(game, 'v19_last_quake_level', 0))
    should = False
    if lvl <= 1:
        should = random.random() < 0.28
    elif since >= 2:
        should = True
    elif since >= 1:
        should = random.random() < 0.46
    if should:
        game.v19_quake_level = True
        game.v19_last_quake_level = lvl
        game.v19_quake_seed = random.randint(1000, 999999)
        game.v19_quake_warning_timer = FPS * 2
        game.v19_quake_impact_pending = True
        game.earthquake_warning_timer = 0
        game.earthquake_timer = 0
        game.quake_red_border_timer = 0
    else:
        game.v19_quake_level = False
globals()['_v19_prepare_quake_level'] = _v19_prepare_quake_level
def _v20_shape_survival_quake_map(game):
    _v20_quake_state(game)
    if game.v19_quake_shaped:
        return
    game.v20_survival_quake_serial += 1
    seed = int(getattr(game, 'v19_quake_seed', 0)) + game.v20_survival_quake_serial * 911
    rng = random.Random(seed)
    start = int(game.cam_x + SCREEN_W + 60)
    end = start + 2100
    x = start + rng.randint(40, 120)
    last_trench = -9999
    while x < end:
        x += rng.randint(135, 260)
        if rng.random() < 0.52 and abs(x - last_trench) > 150:
            _v19_add_quake_trench(game.terrain, x, rng)
            last_trench = x
        else:
            _v19_add_quake_uplift(game.terrain, x, rng)
            if rng.random() < 0.22 and abs(x + 70 - last_trench) > 165:
                _v19_add_quake_trench(game.terrain, x + rng.randint(50, 90), rng)
                last_trench = x
    game.v19_quake_shaped = True
    game.v19_quake_active_timer = FPS * 8
    game.add_shake(20, 7)
def _v20_start_survival_quake(game):
    _v20_quake_state(game)
    game.v19_quake_level = True
    game.v19_quake_shaped = False
    game.v19_quake_seed = random.randint(1000, 999999)
    game.v19_quake_warning_timer = FPS * 2
    game.v19_quake_active_timer = 0
    game.v19_quake_impact_pending = True
    game.earthquake_warning_timer = 0
    game.earthquake_timer = 0
    game.quake_red_border_timer = 0
def _update_earthquake_v20(game):
    _v20_quake_state(game)
    if getattr(game, 'state', '') != 'playing' or _v20_is_multiplayer_context(game):
        game.earthquake_timer = 0
        game.earthquake_warning_timer = 0
        game.quake_red_border_timer = 0
        return
    game.earthquake_timer = 0
    game.earthquake_warning_timer = 0
    game.quake_red_border_timer = 0
    is_surv = False
    try:
        is_surv = game.is_survival()
    except Exception:
        is_surv = False
    if is_surv:
        px = max(getattr(game.player, 'x', 0), getattr(game, 'total_distance', 0))
        if (not getattr(game, 'v19_quake_level', False)
                and px >= getattr(game, 'v20_survival_quake_next_x', 1400)):
            _v20_start_survival_quake(game)
            game.v20_survival_quake_next_x = px + random.randint(1900, 2600)
    else:
        _v19_prepare_quake_level(game)
    if not getattr(game, 'v19_quake_level', False):
        return
    if getattr(game, 'v19_quake_warning_timer', 0) > 0 or getattr(game, 'v19_quake_active_timer', 0) > 0:
        game.field_order = None
        game.field_order_progress = 0
        game.field_order_cooldown = max(getattr(game, 'field_order_cooldown', 0), FPS * 6)
    if game.v19_quake_warning_timer > 0:
        game.v19_quake_warning_timer -= 1
        if pyxel.frame_count % 2 == 0:
            game.add_shake(2, random.randint(3, 6))
        if game.v19_quake_warning_timer <= 0 and game.v19_quake_impact_pending:
            game.v19_quake_impact_pending = False
            if is_surv:
                _v20_shape_survival_quake_map(game)
            else:
                _v19_shape_whole_quake_map(game)
        return
    if game.v19_quake_active_timer > 0:
        game.v19_quake_active_timer -= 1
        game.add_shake(2, random.randint(4, 7))
        if game.v19_quake_active_timer % 45 == 0:
            tx = game.player.x + random.randint(80, 260)
            gy = game.terrain.ground_y_at(tx, game.difficulty_value())
            game.texts.append(FlashText(tx, gy - 26, '!', C8, 28))
        if game.v19_quake_active_timer <= 0 and is_surv:
            game.v19_quake_level = False
        return
globals()['_update_earthquake_v14'] = _update_earthquake_v20
def _draw_earthquake_overlay_v20(game):
    _v20_quake_state(game)
    if _v20_is_multiplayer_context(game) or not getattr(game, 'v19_quake_level', False):
        return
    pyxel.camera(0, 0)
    if getattr(game, 'v19_quake_warning_timer', 0) > 0:
        sec = max(1, (game.v19_quake_warning_timer + FPS - 1) // FPS)
        col = C8 if (pyxel.frame_count // 6) % 2 == 0 else C10
        pyxel.text(98, 10, 'SEISMIC WARNING ' + str(sec), col)
        pyxel.text(78, 20, 'TRENCHES + UPLIFTS INCOMING', C7)
    elif getattr(game, 'v19_quake_active_timer', 0) > 0:
        col = C8 if (pyxel.frame_count // 4) % 2 == 0 else C10
        pyxel.text(102, 10, 'EARTHQUAKE: JUMP GAPS', col)
globals()['_draw_earthquake_overlay_v14'] = _draw_earthquake_overlay_v20
_old_reset_run_v20 = Game.reset_run
def _reset_run_v20(self):
    result = _old_reset_run_v20(self)
    self.v19_last_quake_level = 0
    self.v19_quake_prepared_level = None
    self.v20_survival_quake_next_x = 1250 + random.randint(0, 500)
    self.v20_survival_quake_serial = 0
    if not _v20_is_multiplayer_context(self):
        _v19_prepare_quake_level(self)
    return result
Game.reset_run = _reset_run_v20
def _story_lines_intro_v20(self):
    name = self.current_country()['name']
    return [
        name + ' receives the first strike at dawn.',
        'Bombers cross the border while bunkers wake underground.',
        'Your command signal is the last one still moving.',
        'Reach each bunker in order and force the enemy back.',
    ]
Game.story_lines_intro = _story_lines_intro_v20
def _story_lines_progress_v20(self):
    name = self.current_country()['name']
    key = self.current_country()['key']
    lvl = int(getattr(self, 'level', 1))
    empire = bool(getattr(self, 'empire_mode', False) or lvl > CAMPAIGN_LEVELS)
    if empire:
        empire_name = REAL_EMPIRE_NAMES.get(key, name + ' EMPIRE') if 'REAL_EMPIRE_NAMES' in globals() else name + ' EMPIRE'
        empire_step = max(1, lvl - CAMPAIGN_LEVELS)
        empire_arc = [
            [empire_name + ' begins its first advance.', 'The new arsenal is unstable but alive.', 'Bunker roads now answer your banner.'],
            ['Enemy scouts identify your empire rounds.', 'The front becomes faster and more dangerous.', 'Hold distance and save dash for lasers.'],
            ['The first empire boss signal appears.', 'Command grants one extra HP for reaching it.', 'Survive the laser duel and continue.'],
            ['Earthquakes redraw the captured routes.', 'Raised ground becomes cover and danger.', 'Read the cracks before you jump.'],
            ['Supply lines connect through the bunker chain.', 'Your custom bullets are now feared.', 'Advance without letting the rhythm break.'],
            ['The enemy switches to heavy interception.', 'Only clean movement keeps the empire alive.', 'Every kilometre now matters.'],
            ['Old nations begin to collapse behind you.', 'The bunker network becomes a throne road.', 'Do not waste the spawn shield.'],
            ['The final empire sectors burn ahead.', 'Boss lasers charge faster in the dark.', 'Dash early, then counterattack.'],
            ['The last command vault is exposed.', 'All previous fronts lead to this door.', 'Finish the route and claim the empire.'],
        ]
        return empire_arc[min(len(empire_arc) - 1, (empire_step - 1) // 2)]
    normal_arc = [
        [name + ' secures the first bunker.', 'The route ahead opens under bombardment.', 'Move to Sector 2 before planes return.'],
        ['Sector 2 confirms enemy drones overhead.', 'Supply markers flicker through the smoke.', 'Weapon switching is now part of survival.'],
        ['Sector 3 reports the first boss core.', 'The laser weapon is locked for this fight.', 'Clear the boss before the bunker unlocks.'],
        ['Sector 4 crosses unstable ground.', 'Earthquakes are breaking the whole route.', 'Jump trenches and use uplifts as cover.'],
        ['Sector 5 is the final normal approach.', 'Enemy command throws everything forward.', 'Save rockets and watch the skyline.'],
        ['Sector 6 reaches the ascension bunker.', 'The upgrade core is inside the final door.', 'Win this front and the empire begins.'],
    ]
    return normal_arc[min(len(normal_arc) - 1, max(0, lvl - 1))]
Game.story_lines_progress = _story_lines_progress_v20
def _update_story_update_v20(self):
    self.intro_timer += 1
    if self.intro_timer % 16 == 0 and self.intro_timer < 132:
        self.intro_bombs.append({
            'x': random.randint(42, SCREEN_W - 42),
            'y': -10,
            'vy': random.uniform(1.9, 3.2),
            'tail': random.randint(5, 10),
        })
    new_bombs = []
    for b in self.intro_bombs:
        b['y'] += b.get('vy', 2.4)
        if b['y'] >= 118:
            self.spawn_explosion(b['x'], 118, 14, 'normal')
            self.intro_flash = 5
        else:
            new_bombs.append(b)
    self.intro_bombs = new_bombs
    if getattr(self, 'intro_flash', 0) > 0:
        self.intro_flash -= 1
    for e in self.effects:
        e.update()
    for p in self.particles:
        p.update()
    self.effects = [e for e in self.effects if e.alive]
    self.particles = [p for p in self.particles if p.alive]
    if self.intro_timer > 180 and pyxel.btnp(pyxel.KEY_RETURN):
        self.state = 'playing'
        stop_story_music()
Game.update_story_update = _update_story_update_v20
def _draw_story_update_v20(self):
    pyxel.cls(C0)
    key = self.current_country()['key']
    draw_country_outline(key, 92, 22, scale=4, color=C7, fill=False)
    draw_flag(key, 146, 44, 26, 16, pyxel.frame_count * 0.15)
    if getattr(self, 'intro_flash', 0) > 0 and (pyxel.frame_count // 2) % 2 == 0:
        draw_country_outline(key, 92, 22, scale=4, color=C8, fill=True)
    for i in range(0, SCREEN_W, 20):
        h = 18 + (i * 5) % 30
        pyxel.rect(i, 132 - h, 12, h, C5)
        if (i + pyxel.frame_count) % 60 < 8:
            pyxel.pset(i + 4, 128 - h, C9)
    for b in self.intro_bombs:
        bx, by = int(b['x']), int(b['y'])
        pyxel.line(bx, by - int(b.get('tail', 7)), bx, by, C10)
        pyxel.circ(bx, by, 3, C8)
        pyxel.pset(bx, by, C7)
    for e in self.effects:
        e.draw(0)
    for p in self.particles:
        p.draw(0)
    lines = self.story_lines_progress()
    if self.intro_timer > 16:
        pyxel.text(40, 142, lines[0][:46], C14)
    if self.intro_timer > 72:
        pyxel.text(40, 154, lines[1][:46], C7)
    if self.intro_timer > 128:
        pyxel.text(40, 166, lines[2][:46], C7)
    if self.intro_timer > 180:
        pyxel.text(104, 186, 'PRESS ENTER', C7 if (pyxel.frame_count // 12) % 2 else C14)
Game.draw_story_update = _draw_story_update_v20
def _mp20_difficulty(game):
    return 3.0
def _mp19_start_round(game):
    count = int(clamp(getattr(game, 'mp_player_count', 2), 2, 3))
    game.terrain.reset()
    game.mp_arena_players = []
    game.mp_arena_bullets = []
    game.mp_arena_hazards = []
    game.mp_arena_spawn_cd = 58
    game.mp_arena_timer = 0
    game.cam_x = 0
    game.shake_timer = 0
    game.effects.clear(); game.particles.clear(); game.texts.clear()
    game.field_order = None
    game.v19_quake_level = False
    for i in range(count):
        x = 58 + i * 30
        y = game.terrain.ground_y_at(x + 8, _mp20_difficulty(game)) - PLAYER_H
        empire = i in getattr(game, 'mp_empire_players', set())
        game.mp_arena_players.append({
            'idx': i, 'x': x, 'y': y, 'vx': 0.0, 'vy': 0.0, 'facing': 1,
            'hp': 3, 'alive': True, 'dist': 0, 'invuln': 90, 'shot_cd': 0,
            'empire': empire, 'on_ground': True, 'dead_timer': 0, 'walk': 0,
        })
    game.mp_scores = [0 for _ in range(count)]
    game.state = 'mp_arena'
    stop_story_music()
globals()['_mp19_start_round'] = _mp19_start_round
def _mp19_update_player(game, p):
    keys = _MP19_KEYS[p['idx']]
    if not p.get('alive'):
        p['dead_timer'] = max(0, p.get('dead_timer', 0) - 1)
        return
    if p['invuln'] > 0:
        p['invuln'] -= 1
    if p['shot_cd'] > 0:
        p['shot_cd'] -= 1
    speed = 1.88
    jump = -5.45
    vx = 0
    if pyxel.btn(keys['left']):
        vx -= speed; p['facing'] = -1
    if pyxel.btn(keys['right']):
        vx += speed; p['facing'] = 1
    p['vx'] = vx
    if pyxel.btnp(keys['jump']) and p.get('on_ground'):
        p['vy'] = jump
        p['on_ground'] = False
    if pyxel.btn(keys['down']) and not p.get('on_ground'):
        p['vy'] += 0.35
    p['vy'] = min(p['vy'] + GRAVITY, MAX_FALL)
    p['x'] += p['vx']
    p['y'] += p['vy']
    p['x'] = max(0, p['x'])
    foot_l = p['x'] + 3
    foot_m = p['x'] + 8
    foot_r = p['x'] + 13
    grounds = [game.terrain.ground_y_at(foot_l, _mp20_difficulty(game)), game.terrain.ground_y_at(foot_m, _mp20_difficulty(game)), game.terrain.ground_y_at(foot_r, _mp20_difficulty(game))]
    target_ground = min(grounds)
    if p['y'] >= target_ground - PLAYER_H:
        p['y'] = target_ground - PLAYER_H
        p['vy'] = 0
        p['on_ground'] = True
    else:
        p['on_ground'] = False
    if p['y'] > SCREEN_H + 40:
        _mp19_player_hit(game, p, 3)
    if abs(p['vx']) > 0.1 and p.get('on_ground'):
        p['walk'] = p.get('walk', 0) + 1
    else:
        p['walk'] = 0
    p['dist'] += max(0.55, max(0, p['vx']) + 0.40)
    if pyxel.btnp(keys['shoot']) and p['shot_cd'] <= 0:
        p['shot_cd'] = 15
        game.mp_arena_bullets.append({
            'x': p['x'] + 8 + p['facing'] * 7, 'y': p['y'] + 11,
            'vx': p['facing'] * 6.4, 'vy': 0, 'owner': p['idx'], 'life': 76,
            'style': None, 'seed': random.randint(0, 9999)
        })
        sfx(0, 0)
globals()['_mp19_update_player'] = _mp19_update_player
def _mp20_keep_players_on_screen(game):
    live = [p for p in getattr(game, 'mp_arena_players', []) if p.get('alive')]
    if not live:
        return
    leader_x = max(p['x'] for p in live)
    if leader_x > game.cam_x + SCREEN_W - 56:
        game.cam_x = max(0, leader_x - (SCREEN_W - 56))
    left = game.cam_x + 14
    right = game.cam_x + SCREEN_W - 28
    for p in live:
        old_x = p['x']
        p['x'] = clamp(p['x'], left, right)
        if p['x'] != old_x:
            p['vx'] = 0
def _mp19_update_bullets_and_hazards(game):
    for b in game.mp_arena_bullets:
        b['x'] += b['vx']; b['y'] += b.get('vy', 0); b['life'] -= 1
    game.mp_arena_spawn_cd -= 1
    if game.mp_arena_spawn_cd <= 0:
        game.mp_arena_spawn_cd = max(28, 68 - game.mp_arena_timer // 430)
        if random.random() < 0.70:
            game.mp_arena_hazards.append({'kind': 'missile', 'x': game.cam_x + random.randint(44, SCREEN_W - 34), 'y': -16, 'vy': random.uniform(1.9, 3.2), 'alive': True})
        else:
            mx = game.cam_x + random.randint(70, SCREEN_W - 50)
            gy = game.terrain.ground_y_at(mx, _mp20_difficulty(game))
            game.mp_arena_hazards.append({'kind': 'mine', 'x': mx, 'y': gy - 5, 'timer': random.randint(105, 190), 'alive': True})
    for h in game.mp_arena_hazards:
        if not h.get('alive'):
            continue
        if h['kind'] == 'missile':
            h['y'] += h['vy']
            gy = game.terrain.ground_y_at(h['x'], _mp20_difficulty(game))
            if h['y'] >= gy:
                h['alive'] = False
                game.spawn_explosion(h['x'], gy, 13, 'normal')
                game.add_shake(4, 3)
        elif h['kind'] == 'mine':
            h['timer'] -= 1
            if h['timer'] <= 0:
                h['alive'] = False
                game.spawn_explosion(h['x'], h['y'], 15, 'normal')
    for b in game.mp_arena_bullets:
        if b['life'] <= 0:
            continue
        for h in game.mp_arena_hazards:
            if h.get('alive') and abs(b['x'] - h['x']) < 12 and abs(b['y'] - h['y']) < 14:
                h['alive'] = False; b['life'] = 0
                game.spawn_explosion(h['x'], h['y'], 12, 'normal')
                break
    for h in game.mp_arena_hazards:
        if not h.get('alive'):
            continue
        for p in game.mp_arena_players:
            if p.get('alive') and rects_overlap(p['x'] + 3, p['y'] + 3, 10, 18, h['x'] - 5, h['y'] - 5, 10, 10):
                h['alive'] = False
                _mp19_player_hit(game, p, 1)
                break
    game.mp_arena_bullets = [b for b in game.mp_arena_bullets if b['life'] > 0 and game.cam_x - 40 < b['x'] < game.cam_x + SCREEN_W + 80]
    game.mp_arena_hazards = [h for h in game.mp_arena_hazards if h.get('alive') and game.cam_x - 40 < h['x'] < game.cam_x + SCREEN_W + 80 and h.get('y', 0) < SCREEN_H + 40]
globals()['_mp19_update_bullets_and_hazards'] = _mp19_update_bullets_and_hazards
def _update_mp_arena_v19(self):
    self.mp_arena_timer = getattr(self, 'mp_arena_timer', 0) + 1
    self.field_order = None
    self.v19_quake_level = False
    for p in list(getattr(self, 'mp_arena_players', [])):
        _mp19_update_player(self, p)
    _mp20_keep_players_on_screen(self)
    _mp19_update_bullets_and_hazards(self)
    _mp20_keep_players_on_screen(self)
    for e in self.effects:
        e.update()
    for pa in self.particles:
        pa.update()
    self.effects = [e for e in self.effects if e.alive]
    self.particles = [p for p in self.particles if p.alive]
    self.update_shake()
    live_count = sum(1 for p in self.mp_arena_players if p.get('alive'))
    if live_count <= 1 and self.mp_arena_timer > FPS * 4:
        _mp19_finish_round(self)
    elif self.mp_arena_timer > FPS * 90:
        _mp19_finish_round(self)
globals()['_update_mp_arena_v19'] = _update_mp_arena_v19
def _draw_mp_player_v19(p, cam_x, country_key):
    sx, sy = int(p['x'] - cam_x), int(p['y'])
    idx = p['idx']
    body = Player.SKINS[0]['body']
    cape = Player.SKINS[0]['cape']
    visor = Player.SKINS[0]['visor']
    if p.get('invuln', 0) > 0 and (pyxel.frame_count // 4) % 2 == 0:
        pyxel.circb(sx + 8, sy + 11, 13, C7)
    if not p.get('alive'):
        pyxel.text(sx, sy + 8, 'KO', C8)
        return
    step = 0
    if p.get('on_ground') and abs(p.get('vx', 0)) > 0.1:
        step = 1 if (p.get('walk', 0) // 5) % 2 == 0 else -1
    pyxel.rect(sx + 2 - p.get('facing', 1), sy + 9, 5, 11, cape)
    pyxel.rect(sx + 5, sy + 6, 7, 14, body)
    pyxel.rect(sx + 4, sy + 1, 9, 7, body)
    pyxel.rect(sx + (9 if p.get('facing', 1) > 0 else 4), sy + 3, 3, 2, visor)
    pyxel.rect(sx + 4, sy + 20, 4, 4 + step, C4)
    pyxel.rect(sx + 10, sy + 20, 4, 4 - step, C4)
    pyxel.rect(sx + 2, sy + 23 + step, 6, 2, C0)
    pyxel.rect(sx + 9, sy + 23 - step, 6, 2, C0)
    if p.get('empire'):
        pyxel.text(sx + 4, sy - 16, 'EMP', C10)
    pyxel.text(sx + 2, sy - 8, 'P' + str(idx + 1), _MP19_COLORS[idx % len(_MP19_COLORS)])
globals()['_draw_mp_player_v19'] = _draw_mp_player_v19
def _draw_mp_arena_v19(self):
    self.draw_background()
    shake_x = getattr(self, 'shake_x', 0); shake_y = getattr(self, 'shake_y', 0)
    pyxel.camera(int(shake_x), int(shake_y))
    self.terrain.draw(self.cam_x, _mp20_difficulty(self))
    for h in getattr(self, 'mp_arena_hazards', []):
        sx, sy = int(h['x'] - self.cam_x), int(h['y'])
        if h['kind'] == 'missile':
            pyxel.rect(sx - 3, sy - 9, 6, 14, C8)
            pyxel.pset(sx, sy - 10, C7)
            pyxel.line(sx - 2, sy + 5, sx, sy + 10, C10)
            pyxel.line(sx + 2, sy + 5, sx, sy + 10, C10)
        else:
            pyxel.circ(sx, sy, 5, C5)
            pyxel.circb(sx, sy, 5, C8)
            pyxel.text(sx - 2, sy - 12, '!', C8)
    for b in getattr(self, 'mp_arena_bullets', []):
        sx, sy = int(b['x'] - self.cam_x), int(b['y'])
        tail = 7 if b.get('vx', 0) > 0 else -7
        pyxel.line(sx - tail, sy, sx, sy, C7)
        pyxel.circ(sx, sy, 2, C10)
    country_key = self.current_country()['key']
    for p in getattr(self, 'mp_arena_players', []):
        _draw_mp_player_v19(p, self.cam_x, country_key)
    for e in self.effects:
        e.draw(self.cam_x)
    for pa in self.particles:
        pa.draw(self.cam_x)
    pyxel.camera(0, 0)
    pyxel.rect(0, 0, SCREEN_W, 26, C0)
    pyxel.rectb(0, 0, SCREEN_W, 26, C5)
    pyxel.text(6, 5, 'MP ROUND ' + str(getattr(self, 'mp_round', 1)), C14)
    for p in getattr(self, 'mp_arena_players', []):
        x = 78 + p['idx'] * 80
        pyxel.text(x, 5, 'P' + str(p['idx'] + 1) + ' ' + _v19_survival_km_text(int(p.get('dist', 0))), C10 if p.get('empire') else C7)
        for hp_i in range(3):
            px = x + hp_i * 8
            pyxel.rectb(px, 15, 6, 6, C8)
            if hp_i < max(0, int(p.get('hp', 0))):
                pyxel.rect(px + 1, 16, 4, 4, C8)
        if p.get('empire'):
            pyxel.text(x + 31, 15, 'EMP', C10)
globals()['_draw_mp_arena_v19'] = _draw_mp_arena_v19
FINAL_BUILD_TAG = 'v21-global-pause-red-text-quake-nuke-tutorial-no-laser'
Player.WEAPONS = ["BLASTER", "SPREAD"]
_old_player_update_v21_weapon = Player.update
def _player_update_v21_weapon(self, difficulty, dev_mode=False):
    if getattr(self, 'weapon_index', 0) >= len(self.WEAPONS):
        self.weapon_index = 0
    result = _old_player_update_v21_weapon(self, difficulty, dev_mode)
    if getattr(self, 'weapon_index', 0) >= len(self.WEAPONS):
        self.weapon_index = 0
    return result
Player.update = _player_update_v21_weapon
_old_player_fire_v21 = Player.fire
def _player_fire_v21(self):
    if getattr(self, 'weapon_index', 0) >= len(self.WEAPONS):
        self.weapon_index = 0
    fired_weapon = self.weapon
    bullets, effects = _old_player_fire_v21(self)
    if bullets:
        self.shot_cd = max(4, int(getattr(self, 'shot_cd', 0)) - 1)
        for b in bullets:
            if fired_weapon == 'BLASTER' and not getattr(b, 'empire_style', None):
                b.color = C10
            elif fired_weapon == 'SPREAD' and not getattr(b, 'empire_style', None):
                b.color = C15
    return bullets, effects
Player.fire = _player_fire_v21
_old_update_pause_v21 = Game.update_pause
def _update_pause_v21(self):
    if pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.KEY_DOWN):
        self.pause_index = (getattr(self, 'pause_index', 0) + 1) % 2
    if pyxel.btnp(pyxel.KEY_P) or pyxel.btnp(pyxel.KEY_RETURN):
        if getattr(self, 'pause_index', 0) == 0:
            self.state = getattr(self, '_pause_return_state', 'playing')
        else:
            self.state = 'menu'
            stop_story_music()
        return
    if pyxel.btnp(pyxel.KEY_ESCAPE):
        self.state = getattr(self, '_pause_return_state', 'playing')
Game.update_pause = _update_pause_v21
_old_update_v21_global_pause = Game.update
def _update_v21_global_pause(self):
    globals()['_active_secret_game'] = self
    st = getattr(self, 'state', '')
    if pyxel.btnp(pyxel.KEY_P):
        if st == 'paused':
            self.state = getattr(self, '_pause_return_state', 'playing')
            return
        if st == 'tutorial':
            try:
                self.tutorial_flags['pause'] = True
            except Exception:
                pass
        self._pause_return_state = st
        self.state = 'paused'
        self.pause_index = 0
        return
    return _old_update_v21_global_pause(self)
Game.update = _update_v21_global_pause
_old_draw_paused_v21 = Game.draw_paused
def _draw_paused_v21(self):
    pyxel.camera(0, 0)
    pyxel.rect(48, 52, 224, 92, C0)
    pyxel.rectb(48, 52, 224, 92, C7)
    pyxel.text(142, 68, 'PAUSED', C14)
    ret = str(getattr(self, '_pause_return_state', 'playing')).replace('_', ' ').upper()
    pyxel.text(88, 84, 'RETURN TO: ' + ret[:22], C6)
    opts = ['RESUME', 'MENU']
    for i, opt in enumerate(opts):
        y = 104 + i * 14
        if i == getattr(self, 'pause_index', 0):
            pyxel.rect(112, y - 3, 92, 11, C12)
            pyxel.text(138, y, opt, C0)
        else:
            pyxel.text(138, y, opt, C7)
    pyxel.text(92, 136, 'P / ENTER RESUME   ESC RESUME', C5)
Game.draw_paused = _draw_paused_v21
def _v21_add_quake_trench(terrain, x, rng):
    terrain.craters.append({
        'x': x,
        'r': rng.randint(21, 34),
        'gap_r': rng.randint(7, 12),
        'depth': rng.randint(22, 40),
    })
    if len(terrain.craters) > 90:
        terrain.craters.pop(0)
def _v21_add_quake_uplift(terrain, x, rng):
    if not hasattr(terrain, 'uplifts'):
        terrain.uplifts = []
    terrain.uplifts.append({
        'x': x,
        'r': rng.randint(50, 112),
        'height': rng.randint(20, 46),
    })
    if len(terrain.uplifts) > 90:
        terrain.uplifts.pop(0)
globals()['_v19_add_quake_trench'] = _v21_add_quake_trench
globals()['_v19_add_quake_uplift'] = _v21_add_quake_uplift
def _v21_shape_quake_span(game, start, end, seed):
    rng = random.Random(seed)
    x = start + rng.randint(25, 80)
    last_trench = -9999
    while x < end:
        x += rng.randint(82, 158)
        if x >= end:
            break
        if rng.random() < 0.78 and abs(x - last_trench) > 106:
            _v21_add_quake_trench(game.terrain, x, rng)
            last_trench = x
            if rng.random() < 0.72:
                _v21_add_quake_uplift(game.terrain, x + rng.choice([-1, 1]) * rng.randint(36, 86), rng)
        else:
            _v21_add_quake_uplift(game.terrain, x, rng)
            if rng.random() < 0.42 and abs(x + 72 - last_trench) > 116:
                _v21_add_quake_trench(game.terrain, x + rng.randint(50, 92), rng)
                last_trench = x
        if rng.random() < 0.58:
            _v21_add_quake_uplift(game.terrain, x + rng.randint(-70, 85), rng)
def _v19_shape_whole_quake_map(game):
    if '_v19_quake_state' in globals():
        _v19_quake_state(game)
    if not getattr(game, 'v19_quake_level', False) or getattr(game, 'v19_quake_shaped', False):
        return
    start = int(getattr(game, 'checkpoint_x', 40)) + 120
    try:
        length = _v19_level_len(game)
    except Exception:
        length = LEVEL_DISTANCE
    end = int(getattr(game, 'checkpoint_x', 40)) + length - 100
    _v21_shape_quake_span(game, start, end, int(getattr(game, 'v19_quake_seed', 0)) + int(getattr(game, 'level', 1)) * 1283)
    game.v19_quake_shaped = True
    game.v19_quake_active_timer = FPS * 9
    try:
        game.add_shake(16, 6)
        gx = game.player.x + 94
        gy = game.terrain.ground_y_at(gx, game.difficulty_value())
        game.spawn_explosion(gx, gy, 16, 'normal')
    except Exception:
        pass
globals()['_v19_shape_whole_quake_map'] = _v19_shape_whole_quake_map
def _v20_shape_survival_quake_map(game):
    if '_v20_quake_state' in globals():
        _v20_quake_state(game)
    if getattr(game, 'v19_quake_shaped', False):
        return
    game.v20_survival_quake_serial = getattr(game, 'v20_survival_quake_serial', 0) + 1
    start = int(game.cam_x + SCREEN_W + 40)
    end = start + 2400
    _v21_shape_quake_span(game, start, end, int(getattr(game, 'v19_quake_seed', 0)) + game.v20_survival_quake_serial * 1301)
    game.v19_quake_shaped = True
    game.v19_quake_active_timer = FPS * 9
    game.add_shake(16, 6)
globals()['_v20_shape_survival_quake_map'] = _v20_shape_survival_quake_map
def _draw_earthquake_overlay_v21(game):
    try:
        _v20_quake_state(game)
    except Exception:
        pass
    if _v20_is_multiplayer_context(game) or not getattr(game, 'v19_quake_level', False):
        return
    pyxel.camera(0, 0)
    if getattr(game, 'v19_quake_warning_timer', 0) > 0:
        sec = max(1, (game.v19_quake_warning_timer + FPS - 1) // FPS)
        col = C8 if (pyxel.frame_count // 5) % 2 == 0 else C10
        pyxel.text(82, 9, '!!! EARTHQUAKE WARNING ' + str(sec) + ' !!!', col)
        pyxel.text(62, 20, 'TRENCHES + UPLIFTS AHEAD', C8)
    elif getattr(game, 'v19_quake_active_timer', 0) > 0:
        col = C8 if (pyxel.frame_count // 4) % 2 == 0 else C10
        pyxel.text(94, 9, 'EARTHQUAKE - WATCH RED ! MARKS', col)
        pyxel.text(83, 20, 'JUMP TRENCHES / USE UPLIFTS', C8)
    if getattr(game, 'nuke_aftershock_timer', 0) > 0:
        pyxel.text(98, 34, 'NUKE AFTERSHOCK', C8 if (pyxel.frame_count // 3) % 2 else C10)
globals()['_draw_earthquake_overlay_v14'] = _draw_earthquake_overlay_v21
_old_update_earthquake_v21 = globals().get('_update_earthquake_v14')
def _update_earthquake_v21(game):
    if _old_update_earthquake_v21:
        _old_update_earthquake_v21(game)
    if getattr(game, 'nuke_aftershock_timer', 0) > 0 and getattr(game, 'state', '') == 'playing' and not _v20_is_multiplayer_context(game):
        game.nuke_aftershock_timer -= 1
        if pyxel.frame_count % 2 == 0:
            game.add_shake(2, random.randint(4, 6))
        if game.nuke_aftershock_timer % 24 == 0:
            rng = random.Random(getattr(game, 'nuke_aftershock_seed', 0) + game.nuke_aftershock_timer)
            base_x = getattr(game, 'nuke_aftershock_x', game.player.x + 160)
            tx = base_x + rng.randint(-150, 220)
            if rng.random() < 0.55:
                _v21_add_quake_uplift(game.terrain, tx, rng)
            else:
                _v21_add_quake_trench(game.terrain, tx, rng)
            try:
                gy = game.terrain.ground_y_at(tx, game.difficulty_value())
                game.texts.append(FlashText(tx, gy - 28, '!', C8, 34))
            except Exception:
                pass
globals()['_update_earthquake_v14'] = _update_earthquake_v21
_old_add_shake_v21 = Game.add_shake
def _add_shake_v21(self, duration, intensity):
    if (getattr(self, 'v19_quake_active_timer', 0) > 0 or getattr(self, 'v19_quake_warning_timer', 0) > 0 or getattr(self, 'nuke_aftershock_timer', 0) > 0):
        intensity = min(intensity, 6)
        duration = min(duration, 16)
    return _old_add_shake_v21(self, duration, intensity)
Game.add_shake = _add_shake_v21
def _bomber_spawn_chance_v21(self):
    if _v20_boss_active(self) or _v20_is_multiplayer_context(self):
        return 0
    d = self.difficulty_value()
    if d < 2.0:
        return 0
    quake_soften = 0.70 if getattr(self, 'v19_quake_active_timer', 0) > 0 else 1.0
    empire_soften = 0.92 if getattr(self, 'empire_mode', False) else 1.0
    return min(0.00125 + d * 0.00032, 0.0115) * quake_soften * empire_soften
Game.bomber_spawn_chance = _bomber_spawn_chance_v21
def _bomber_draw_v21(self, cam_x):
    sx = int(self.x - cam_x)
    sy = int(self.y)
    body = C7 if self.flash > 0 else C12
    pyxel.rect(sx + 28, sy + 12, 100, 12, body)
    pyxel.rect(sx + 20, sy + 14, 116, 8, C6)
    if self.dir == 1:
        pyxel.tri(sx + 128, sy + 12, sx + 154, sy + 18, sx + 128, sy + 24, body)
        pyxel.rect(sx + 136, sy + 15, 8, 4, C7)
    else:
        pyxel.tri(sx + 28, sy + 12, sx + 2, sy + 18, sx + 28, sy + 24, body)
        pyxel.rect(sx + 10, sy + 15, 8, 4, C7)
    pyxel.tri(sx + 52, sy + 14, sx + 14, sy + 38, sx + 70, sy + 20, C1)
    pyxel.tri(sx + 104, sy + 14, sx + 142, sy + 38, sx + 86, sy + 20, C1)
    pyxel.tri(sx + 62, sy + 14, sx + 28, sy - 8, sx + 78, sy + 12, C1)
    pyxel.tri(sx + 94, sy + 14, sx + 128, sy - 8, sx + 78, sy + 12, C1)
    for ex in (54, 72, 90, 108):
        pyxel.rect(sx + ex, sy + 24, 8, 5, C4)
        if self.anim % 4 < 2:
            pyxel.pset(sx + ex + 3, sy + 30, C9)
    if getattr(self, 'warning_phase', False) and not getattr(self, 'has_dropped', False):
        n = max(1, int(math.ceil(max(0, getattr(self, 'drop_cd', FPS)) / FPS)))
        col = C8 if (pyxel.frame_count // 5) % 2 == 0 else C10
        pyxel.text(sx + 52, sy - 24, '!!! NUKE ' + str(n) + ' !!!', col)
        pyxel.text(sx + 48, sy - 14, 'IMPACT = 3s QUAKE', C8)
BomberEnemy.draw = _bomber_draw_v21
_old_spawn_explosion_v21 = Game.spawn_explosion
def _spawn_explosion_v21(self, x, y, power=12, kind='normal'):
    result = _old_spawn_explosion_v21(self, x, y, power, kind)
    try:
        near_ground = abs(y - self.terrain.ground_y_at(x, self.difficulty_value())) < 18
    except Exception:
        near_ground = False
    if kind == 'nuke' and near_ground and not _v20_is_multiplayer_context(self):
        self.nuke_aftershock_timer = max(getattr(self, 'nuke_aftershock_timer', 0), FPS * 3)
        self.nuke_aftershock_x = x
        self.nuke_aftershock_seed = random.randint(0, 999999)
        if getattr(self, 'state', '') == 'playing':
            self.add_text(x - 30, y - 38, 'NUKE AFTERSHOCK!', C8)
    return result
Game.spawn_explosion = _spawn_explosion_v21
_old_player_hit_v21_flash = Game.player_hit
def _player_hit_v21_flash(self, dmg=1):
    before = getattr(self.player, 'hp', 0)
    result = _old_player_hit_v21_flash(self, dmg)
    if getattr(self.player, 'hp', 0) < before:
        self.damage_flash_timer = 3
    return result
Game.player_hit = _player_hit_v21_flash
def _draw_damage_flash_v14(game):
    if getattr(game, 'damage_flash_timer', 0) <= 0:
        return
    pyxel.camera(0, 0)
    pyxel.rect(0, 0, SCREEN_W, SCREEN_H, C0)
globals()['_draw_damage_flash_v14'] = _draw_damage_flash_v14
_old_draw_menu_v21 = Game.draw_menu
def _draw_menu_v21(self):
    _old_draw_menu_v21(self)
    pyxel.camera(0, 0)
    pyxel.rect(214, 114, 74, 13, C0)
    pyxel.rectb(214, 114, 74, 13, C10)
    pyxel.text(219, 118, 'BALANCE ' + str(getattr(self, 'coins_total', 0)) + '$', C10)
    if self.skin_index not in self.unlocked_skins:
        cost = SKIN_PRICES[self.skin_index]
        col = C10 if getattr(self, 'coins_total', 0) >= cost else C8
        pyxel.text(215, 132, 'U BUY: ' + str(cost) + '$', col)
Game.draw_menu = _draw_menu_v21
_TUTORIAL_ORDER_V21 = ['jump', 'move', 'weapon', 'balloon', 'rocket', 'bomb', 'dash', 'pause']
def _current_tutorial_need_v21(game):
    if getattr(game, 'tutorial_step', 0) >= len(_TUTORIAL_ORDER_V21):
        return 'done'
    return _TUTORIAL_ORDER_V21[game.tutorial_step]
def _tutorial_instruction_v21(game):
    steps = [
        '1) W JUMP',
        '2) A/D MOVE',
        '3) SHIFT: BLASTER <-> SPREAD',
        '4) ARROWS + BLASTER: POP BALLOON',
        '5) K ROCKET: HOMES INTO TARGETS',
        '6) J BOMB: ARC SHOT + MAKES TRENCH',
        '7) SPACE DASH: SHORT INVULN BURST',
        '8) P PAUSE: WORKS IN EVERY MODE',
        'TRAINING COMPLETE',
    ]
    return steps[min(getattr(game, 'tutorial_step', 0), len(steps)-1)]
globals()['_tutorial_instruction_v15'] = _tutorial_instruction_v21
globals()['_tutorial_instruction_v13'] = _tutorial_instruction_v21
class _TutorialTarget:
    def __init__(self, x, y, w=22, h=14):
        self.x = x - w / 2
        self.y = y - h / 2
        self.w = w
        self.h = h
        self.alive = True
def _tutorial_targets_for_rocket(game):
    dr = getattr(game, 'tutorial_drone', {'x': 226, 'y': 88, 'alive': True})
    if dr.get('alive', True):
        return [_TutorialTarget(dr.get('x', 226), dr.get('y', 88), 26, 14)]
    return []
_old_setup_tutorial_v21 = globals().get('_setup_tutorial_v15') or globals().get('_setup_tutorial_v13')
def _setup_tutorial_v21(game):
    if _old_setup_tutorial_v21:
        _old_setup_tutorial_v21(game)
    game.tutorial_flags = {k: False for k in _TUTORIAL_ORDER_V21}
    game.tutorial_step = 0
    game.tutorial_powerup_intro_seen = True
    game.player.weapon_index = 0
    game.player.rockets = 99
    game.player.bombs = 99
    game.tutorial_bomb_marker = {'x': 235, 'alive': True}
globals()['_setup_tutorial_v15'] = _setup_tutorial_v21
globals()['_setup_tutorial_v13'] = _setup_tutorial_v21
def _update_tutorial_v21(self):
    if not hasattr(self, 'tutorial_flags') or 'weapon' not in getattr(self, 'tutorial_flags', {}):
        _setup_tutorial_v21(self)
    if pyxel.btnp(pyxel.KEY_RETURN):
        self.state = 'playing'
        return
    self.player.rockets = 99
    self.player.bombs = 99
    if getattr(self.player, 'weapon_index', 0) >= len(self.player.WEAPONS):
        self.player.weapon_index = 0
    need_before = _current_tutorial_need_v21(self)
    if pyxel.btnp(pyxel.KEY_P):
        self.tutorial_flags['pause'] = True
    if pyxel.btnp(pyxel.KEY_SHIFT):
        self.tutorial_flags['weapon'] = True
    old_x, old_y = self.player.x, self.player.y
    self.player.update(self.difficulty_value(), getattr(self, 'dev_mode', False))
    self.player.x = clamp(self.player.x, 8, 292)
    self.cam_x = 0
    if abs(self.player.x - old_x) > .4:
        self.tutorial_flags['move'] = True
    if pyxel.btnp(pyxel.KEY_W) or self.player.y < old_y - 1:
        self.tutorial_flags['jump'] = True
    if pyxel.btnp(pyxel.KEY_SPACE) or getattr(self.player, 'dash_timer', 0) > 0:
        self.tutorial_flags['dash'] = True
    self.handle_player_fire()
    for b in self.player_bullets:
        b.update()
    rocket_targets = _tutorial_targets_for_rocket(self)
    for r in self.rockets:
        r.update(rocket_targets)
    for bomb in self.bombs:
        bomb.update()
        if bomb.alive:
            gy = self.terrain.ground_y_at(bomb.x, self.difficulty_value())
            if not self.terrain.is_gap_at(bomb.x, self.difficulty_value()) and bomb.y >= gy:
                bomb.alive = False
                self.terrain.add_crater(bomb.x, 24)
                self.spawn_explosion(bomb.x, gy, 18, 'nuke')
                self.add_text(bomb.x - 22, gy - 28, 'TRENCH MADE', C10)
                self.tutorial_flags['bomb'] = True
    for e in self.effects:
        e.update()
    for p in self.particles:
        p.update()
    for t in self.texts:
        t.update()
    need = _current_tutorial_need_v21(self)
    if need == 'balloon':
        bal = self.tutorial_balloon
        if bal.get('alive'):
            for b in self.player_bullets:
                if dist2(b.x, b.y, bal['x'], bal['y']) < 14 * 14:
                    bal['alive'] = False; b.alive = False; self.tutorial_flags['balloon'] = True
                    self.spawn_explosion(bal['x'], bal['y'], 12, 'muzzle')
                    self.add_text(bal['x'], bal['y'] - 18, 'BALLOON POPPED', C14)
    elif need == 'rocket':
        dr = self.tutorial_drone
        dr['arrive'] = min(60, dr.get('arrive', 0) + 2)
        if dr.get('alive'):
            for r in self.rockets:
                if dist2(r.x, r.y, dr['x'], dr['y']) < 22 * 22:
                    dr['alive'] = False; r.alive = False; self.tutorial_flags['rocket'] = True
                    self.spawn_explosion(dr['x'], dr['y'], 18, 'normal')
                    self.add_text(dr['x'] - 12, dr['y'] - 20, 'HOMING HIT', C10)
    elif need == 'bomb':
        tu = self.tutorial_turret
        tu['rise'] = min(24, tu.get('rise', 0) + 2)
        tx = tu['x']; ty = self.terrain.ground_y_at(tx, self.difficulty_value()) - 10
        if tu.get('alive'):
            for bomb in self.bombs:
                if abs(bomb.x - tx) < 26 and abs(bomb.y - ty) < 40:
                    tu['alive'] = False; bomb.alive = False; self.tutorial_flags['bomb'] = True
                    self.terrain.add_crater(tx, 24)
                    self.spawn_explosion(tx, ty, 20, 'nuke')
                    self.add_text(tx - 22, ty - 20, 'BOMB TRENCH', C10)
    self.player_bullets = [b for b in self.player_bullets if b.alive]
    self.rockets = [r for r in self.rockets if r.alive]
    self.bombs = [b for b in self.bombs if b.alive]
    self.effects = [e for e in self.effects if e.alive]
    self.particles = [p for p in self.particles if p.alive]
    self.texts = [t for t in self.texts if t.alive]
    old_step = self.tutorial_step
    while self.tutorial_step < len(_TUTORIAL_ORDER_V21) and self.tutorial_flags.get(_TUTORIAL_ORDER_V21[self.tutorial_step], False):
        self.tutorial_step += 1
    if self.tutorial_step != old_step or need_before != _current_tutorial_need_v21(self):
        self.tutorial_target_intro = 0
    else:
        self.tutorial_target_intro = min(60, getattr(self, 'tutorial_target_intro', 0) + 1)
    if self.tutorial_step >= len(_TUTORIAL_ORDER_V21):
        self.add_text(self.player.x + 10, 42, 'TRAINING COMPLETE', C14)
        self._tutorial_done_timer = getattr(self, '_tutorial_done_timer', FPS * 2) - 1
        if self._tutorial_done_timer <= 0:
            self.state = 'playing'
Game.update_tutorial = _update_tutorial_v21
def _draw_training_map_v21(game):
    pyxel.cls(C0)
    for i in range(0, SCREEN_W, 18):
        h = 18 + (i * 7) % 34
        pyxel.rect(i, 132 - h, 10, h, C5)
        if i % 36 == 0:
            pyxel.pset(i + 5, 130 - h, C8)
    game.terrain.draw(0, game.difficulty_value())
    pyxel.text(8, 6, _tutorial_instruction_v21(game), C7)
    pyxel.text(8, 16, 'ENTER SKIP', C6)
    pyxel.text(8, 28, 'UNLIMITED ROCKETS + BOMBS HERE', C10)
    labels = [('W JUMP','jump'),('A/D MOVE','move'),('SHIFT WEAPON','weapon'),('ARROWS BALLOON','balloon'),('K ROCKET DRONE','rocket'),('J BOMB TRENCH','bomb'),('SPACE DASH','dash'),('P PAUSE','pause')]
    y = 42
    for idx, (label, flag) in enumerate(labels):
        col = C10 if game.tutorial_flags.get(flag) else (C14 if idx == game.tutorial_step else C5)
        pyxel.text(8, y, ('OK ' if game.tutorial_flags.get(flag) else '-- ') + label, col)
        y += 8
    need = _current_tutorial_need_v21(game)
    intro = clamp(getattr(game, 'tutorial_target_intro', 0) / 28, 0, 1)
    if need == 'balloon' and game.tutorial_balloon.get('alive'):
        bal = game.tutorial_balloon; bx = int(bal['x']); by = int(-24 + (bal['y'] + 24) * intro)
        pyxel.circ(bx, by, 8, C8); pyxel.circ(bx - 2, by - 2, 3, C14); pyxel.line(bx, by + 8, bx, by + 22, C7)
        pyxel.text(bx - 31, by - 20, 'POP BALLOON', C14)
    elif need == 'rocket' and game.tutorial_drone.get('alive'):
        dr = game.tutorial_drone; dx = int(SCREEN_W + 30 - (SCREEN_W + 30 - dr['x']) * intro); dy = dr['y']
        pyxel.rect(dx - 12, dy - 5, 24, 10, C5); pyxel.rectb(dx - 12, dy - 5, 24, 10, C12)
        pyxel.circ(dx - 16, dy, 3, C7); pyxel.circ(dx + 16, dy, 3, C7)
        pyxel.text(168, 48, 'K ROCKET HOMES', C10)
    elif need == 'bomb' and game.tutorial_turret.get('alive'):
        tu = game.tutorial_turret; tx = tu['x']; ground = game.terrain.ground_y_at(tx, game.difficulty_value())
        rise = int(24 * intro); ty = ground - 10 + (24 - rise)
        pyxel.rect(tx - 8, ty, 16, 10, C4); pyxel.rectb(tx - 8, ty, 16, 10, C8); pyxel.line(tx, ty, tx + 12, ty - 8, C7)
        pyxel.text(166, 126, 'J BOMB = TRENCH', C8)
    elif need == 'weapon':
        pyxel.rect(176, 54, 110, 35, C0); pyxel.rectb(176, 54, 110, 35, C14)
        pyxel.text(182, 60, 'BLASTER: FAST STRAIGHT', C10)
        pyxel.text(182, 69, 'SPREAD: WIDE SLOWER', C15)
        pyxel.text(182, 78, 'NO PLAYER LASER GUN', C8)
    pyxel.rect(120, 4, 192, 24, C0)
    pyxel.rectb(120, 4, 192, 24, C6)
    pyxel.text(126, 9, 'POWERUPS: RAPID / SHIELD / SPREAD', C10)
    pyxel.text(126, 18, 'ROCKET+BOMB AMMO / OVERDRIVE', C7)
    for obj_list in (game.player_bullets, game.rockets, game.bombs, game.effects, game.particles, game.texts):
        for o in obj_list:
            if hasattr(o, 'draw'):
                o.draw(0)
    game.player.draw(0)
globals()['_draw_training_map_v15'] = _draw_training_map_v21
globals()['_draw_training_map_v13'] = _draw_training_map_v21
def _draw_tutorial_v21(self):
    if not hasattr(self, 'tutorial_flags') or 'weapon' not in getattr(self, 'tutorial_flags', {}):
        _setup_tutorial_v21(self)
    _draw_training_map_v21(self)
Game.draw_tutorial = _draw_tutorial_v21
FINAL_BUILD_TAG = 'v22-quake-red-text-capped-uplifts-visible-nukes-clean-tutorial'
_V22_UPLIFT_TOP_Y = 88
_V22_TEXT_KILL = {
    'STUNNED 2s', 'LANDMINE STUN', 'BOMB TRENCH', 'TRENCH MADE',
    'NUKE DROP', 'NUKE AFTERSHOCK!', 'BOMB TRENCH', 'BOMB = TRENCH'
}
def _v22_bold_red_text(x, y, msg, col=C8):
    pyxel.text(int(x), int(y), str(msg), col)
    pyxel.text(int(x) + 1, int(y), str(msg), col)
def _v22_clean_texts(game):
    try:
        game.texts = [t for t in game.texts if str(getattr(t, 'text', '')) not in _V22_TEXT_KILL]
    except Exception:
        pass
_old_terrain_ground_y_at_v22_cap = Terrain.ground_y_at
def _terrain_ground_y_at_v22_cap(self, x, difficulty):
    y = _old_terrain_ground_y_at_v22_cap(self, x, difficulty)
    try:
        for u in getattr(self, 'uplifts', []):
            if abs(x - u.get('x', 0)) < max(1, u.get('r', 1)):
                return max(y, _V22_UPLIFT_TOP_Y)
    except Exception:
        pass
    return y
Terrain.ground_y_at = _terrain_ground_y_at_v22_cap
def _v22_add_quake_trench(terrain, x, rng):
    terrain.craters.append({
        'x': x,
        'r': rng.randint(20, 32),
        'gap_r': rng.randint(7, 11),
        'depth': rng.randint(20, 36),
    })
    if len(terrain.craters) > 110:
        terrain.craters.pop(0)
def _v22_add_quake_uplift(terrain, x, rng):
    if not hasattr(terrain, 'uplifts'):
        terrain.uplifts = []
    terrain.uplifts.append({
        'x': x,
        'r': rng.randint(42, 96),
        'height': rng.randint(13, 32),
    })
    if len(terrain.uplifts) > 125:
        terrain.uplifts.pop(0)
globals()['_v19_add_quake_trench'] = _v22_add_quake_trench
globals()['_v19_add_quake_uplift'] = _v22_add_quake_uplift
def _v22_mark_crack(game, x, rng):
    try:
        gy = game.terrain.ground_y_at(x, game.difficulty_value())
        if rng.random() < 0.58:
            game.texts.append(FlashText(x - 4, gy - rng.randint(22, 34), '!', C8, rng.randint(28, 46)))
        if rng.random() < 0.24:
            game.texts.append(FlashText(x - 16, gy - rng.randint(30, 42), 'CRACK!', C8, rng.randint(30, 48)))
    except Exception:
        pass
def _v22_shape_quake_span(game, start, end, seed):
    rng = random.Random(seed)
    x = start + rng.randint(20, 70)
    last_trench = -9999
    while x < end:
        x += rng.randint(68, 126)
        if x >= end:
            break
        made_trench = False
        if rng.random() < 0.74 and abs(x - last_trench) > 92:
            _v22_add_quake_trench(game.terrain, x, rng)
            _v22_mark_crack(game, x, rng)
            last_trench = x
            made_trench = True
            if rng.random() < 0.82:
                _v22_add_quake_uplift(game.terrain, x + rng.choice([-1, 1]) * rng.randint(36, 88), rng)
        if (not made_trench) or rng.random() < 0.72:
            _v22_add_quake_uplift(game.terrain, x + rng.randint(-62, 74), rng)
        if rng.random() < 0.54:
            ux = x + rng.randint(-105, 112)
            _v22_add_quake_uplift(game.terrain, ux, rng)
        if rng.random() < 0.28 and abs(x + 70 - last_trench) > 98:
            tx = x + rng.randint(48, 84)
            _v22_add_quake_trench(game.terrain, tx, rng)
            _v22_mark_crack(game, tx, rng)
            last_trench = tx
def _v19_shape_whole_quake_map(game):
    if '_v19_quake_state' in globals():
        _v19_quake_state(game)
    if _v20_boss_active(game) or not getattr(game, 'v19_quake_level', False) or getattr(game, 'v19_quake_shaped', False):
        return
    start = int(getattr(game, 'checkpoint_x', 40)) + 120
    try:
        length = _v19_level_len(game)
    except Exception:
        length = LEVEL_DISTANCE
    end = int(getattr(game, 'checkpoint_x', 40)) + length - 100
    _v22_shape_quake_span(game, start, end, int(getattr(game, 'v19_quake_seed', 0)) + int(getattr(game, 'level', 1)) * 1847)
    game.v19_quake_shaped = True
    game.v19_quake_active_timer = FPS * 9
    try:
        game.add_shake(14, 5)
        gx = game.player.x + 96
        gy = game.terrain.ground_y_at(gx, game.difficulty_value())
        game.spawn_explosion(gx, gy, 13, 'normal')
    except Exception:
        pass
globals()['_v19_shape_whole_quake_map'] = _v19_shape_whole_quake_map
def _v20_shape_survival_quake_map(game):
    if '_v20_quake_state' in globals():
        _v20_quake_state(game)
    if _v20_boss_active(game) or getattr(game, 'v19_quake_shaped', False):
        return
    game.v20_survival_quake_serial = getattr(game, 'v20_survival_quake_serial', 0) + 1
    start = int(game.cam_x + SCREEN_W + 40)
    end = start + 2300
    _v22_shape_quake_span(game, start, end, int(getattr(game, 'v19_quake_seed', 0)) + game.v20_survival_quake_serial * 1741)
    game.v19_quake_shaped = True
    game.v19_quake_active_timer = FPS * 8
    game.add_shake(13, 5)
globals()['_v20_shape_survival_quake_map'] = _v20_shape_survival_quake_map
def _v19_prepare_quake_level(game):
    _v20_quake_state(game)
    if _v20_boss_active(game) or not _v19_campaign_like(game):
        game.v19_quake_level = False
        game.v19_quake_prepared_level = None
        return
    lvl = int(getattr(game, 'level', 1))
    if lvl <= 1:
        game.v19_quake_level = False
        game.v19_quake_prepared_level = lvl
        game.v19_quake_shaped = False
        game.v19_quake_warning_timer = 0
        game.v19_quake_active_timer = 0
        return
    if getattr(game, 'v19_quake_prepared_level', None) == lvl:
        return
    game.v19_quake_prepared_level = lvl
    game.v19_quake_shaped = False
    game.v19_quake_active_timer = 0
    game.v19_quake_warning_timer = 0
    game.v19_quake_impact_pending = False
    since = lvl - int(getattr(game, 'v19_last_quake_level', 0))
    should = since >= 2 or (since >= 1 and random.random() < 0.52)
    if should:
        game.v19_quake_level = True
        game.v19_last_quake_level = lvl
        game.v19_quake_seed = random.randint(1000, 999999)
        game.v19_quake_warning_timer = FPS * 2
        game.v19_quake_impact_pending = True
        game.earthquake_warning_timer = 0
        game.earthquake_timer = 0
        game.quake_red_border_timer = 0
    else:
        game.v19_quake_level = False
globals()['_v19_prepare_quake_level'] = _v19_prepare_quake_level
def _v22_start_survival_quake(game):
    _v20_quake_state(game)
    if _v20_boss_active(game):
        return
    game.v19_quake_level = True
    game.v19_quake_shaped = False
    game.v19_quake_seed = random.randint(1000, 999999)
    game.v19_quake_warning_timer = FPS * 2
    game.v19_quake_active_timer = 0
    game.v19_quake_impact_pending = True
    game.earthquake_warning_timer = 0
    game.earthquake_timer = 0
    game.quake_red_border_timer = 0
def _update_earthquake_v22(game):
    _v20_quake_state(game)
    if getattr(game, 'state', '') != 'playing' or _v20_is_multiplayer_context(game) or _v20_boss_active(game):
        game.earthquake_timer = 0
        game.earthquake_warning_timer = 0
        game.quake_red_border_timer = 0
        game.v19_quake_warning_timer = 0
        game.v19_quake_active_timer = 0
        game.v19_quake_impact_pending = False
        if _v20_boss_active(game):
            game.nuke_aftershock_timer = 0
        return
    game.earthquake_timer = 0
    game.earthquake_warning_timer = 0
    game.quake_red_border_timer = 0
    is_surv = False
    try:
        is_surv = game.is_survival()
    except Exception:
        pass
    if getattr(game, 'nuke_aftershock_timer', 0) > 0:
        game.nuke_aftershock_timer -= 1
        if pyxel.frame_count % 3 == 0:
            game.add_shake(2, random.randint(2, 4))
        if game.nuke_aftershock_timer % 30 == 0:
            rng = random.Random(getattr(game, 'nuke_aftershock_seed', 0) + game.nuke_aftershock_timer)
            base_x = getattr(game, 'nuke_aftershock_x', game.player.x + 150)
            tx = base_x + rng.randint(-90, 130)
            if rng.random() < 0.46:
                _v22_add_quake_trench(game.terrain, tx, rng)
                _v22_mark_crack(game, tx, rng)
            else:
                _v22_add_quake_uplift(game.terrain, tx, rng)
    if is_surv:
        px = max(getattr(game.player, 'x', 0), getattr(game, 'total_distance', 0))
        if (not getattr(game, 'v19_quake_level', False)
                and px >= getattr(game, 'v20_survival_quake_next_x', 1400)):
            _v22_start_survival_quake(game)
            game.v20_survival_quake_next_x = px + random.randint(1900, 2600)
    else:
        _v19_prepare_quake_level(game)
    if not getattr(game, 'v19_quake_level', False):
        return
    if getattr(game, 'v19_quake_warning_timer', 0) > 0 or getattr(game, 'v19_quake_active_timer', 0) > 0:
        game.field_order = None
        game.field_order_progress = 0
        game.field_order_cooldown = max(getattr(game, 'field_order_cooldown', 0), FPS * 6)
    if game.v19_quake_warning_timer > 0:
        game.v19_quake_warning_timer -= 1
        if pyxel.frame_count % 3 == 0:
            game.add_shake(2, random.randint(2, 5))
        if game.v19_quake_warning_timer <= 0 and game.v19_quake_impact_pending:
            game.v19_quake_impact_pending = False
            if is_surv:
                _v20_shape_survival_quake_map(game)
            else:
                _v19_shape_whole_quake_map(game)
        return
    if game.v19_quake_active_timer > 0:
        game.v19_quake_active_timer -= 1
        game.add_shake(2, random.randint(3, 5))
        if game.v19_quake_active_timer % 42 == 0:
            rng = random.Random(game.v19_quake_active_timer + int(game.player.x))
            tx = game.player.x + rng.randint(70, 260)
            gy = game.terrain.ground_y_at(tx, game.difficulty_value())
            game.texts.append(FlashText(tx, gy - 28, '!', C8, 32))
        if game.v19_quake_active_timer <= 0 and is_surv:
            game.v19_quake_level = False
        return
globals()['_update_earthquake_v14'] = _update_earthquake_v22
def _draw_earthquake_overlay_v22(game):
    if _v20_is_multiplayer_context(game) or _v20_boss_active(game):
        return
    pyxel.camera(0, 0)
    if getattr(game, 'v19_quake_level', False) and getattr(game, 'v19_quake_warning_timer', 0) > 0:
        sec = max(1, (game.v19_quake_warning_timer + FPS - 1) // FPS)
        _v22_bold_red_text(60, 78, '!!! EARTHQUAKE WARNING ' + str(sec) + ' !!!', C8)
        _v22_bold_red_text(70, 90, 'RED ! = BREAKING TRENCH', C8)
    elif getattr(game, 'v19_quake_level', False) and getattr(game, 'v19_quake_active_timer', 0) > 0:
        _v22_bold_red_text(78, 12, '!!! EARTHQUAKE !!!', C8)
        _v22_bold_red_text(73, 24, 'WATCH THE RED ! CRACKS', C8)
    if getattr(game, 'nuke_aftershock_timer', 0) > 0:
        _v22_bold_red_text(92, 84, '!!! AFTERSHOCK !!!', C8)
        _v22_bold_red_text(128, 96, '!', C8)
globals()['_draw_earthquake_overlay_v14'] = _draw_earthquake_overlay_v22
def _bomber_spawn_chance_v22(self):
    if _v20_boss_active(self) or _v20_is_multiplayer_context(self):
        return 0
    d = self.difficulty_value()
    if d < 2.25:
        return 0
    quake_soften = 0.66 if (getattr(self, 'v19_quake_active_timer', 0) > 0 or getattr(self, 'v19_quake_warning_timer', 0) > 0) else 1.0
    empire_soften = 0.88 if getattr(self, 'empire_mode', False) else 1.0
    return min(0.00095 + d * 0.00024, 0.0075) * quake_soften * empire_soften
Game.bomber_spawn_chance = _bomber_spawn_chance_v22
def _bomber_update_v22(self, game):
    self.anim += 1
    self.x += self.vx
    self.update_flash()
    if _v20_boss_active(game):
        self.alive = False
        return
    center = self.x + self.w // 2
    in_screen = game.cam_x + 18 <= center <= game.cam_x + SCREEN_W - 18
    if not self.has_dropped:
        if in_screen and (self.warning_phase or abs(center - self.drop_x) < 120):
            self.warning_phase = True
            self.nuke_countdown_started = True
            self.drop_cd -= 1
            if self.drop_cd <= 0 and in_screen:
                self.has_dropped = True
                bomb = HeavyBombEnemy(center - 9, self.y + 24, 2.15)
                bomb.nuke_trench = True
                game.enemies.append(bomb)
        elif not in_screen:
            self.warning_phase = False
            self.drop_cd = max(getattr(self, 'drop_cd', FPS * 3), FPS)
    if self.x < game.cam_x - 360 or self.x > game.cam_x + SCREEN_W + 360:
        self.alive = False
BomberEnemy.update = _bomber_update_v22
def _bomber_draw_v22(self, cam_x):
    sx = int(self.x - cam_x)
    sy = int(self.y)
    body = C7 if self.flash > 0 else C12
    pyxel.rect(sx + 28, sy + 12, 100, 12, body)
    pyxel.rect(sx + 20, sy + 14, 116, 8, C6)
    if self.dir == 1:
        pyxel.tri(sx + 128, sy + 12, sx + 154, sy + 18, sx + 128, sy + 24, body)
        pyxel.rect(sx + 136, sy + 15, 8, 4, C7)
    else:
        pyxel.tri(sx + 28, sy + 12, sx + 2, sy + 18, sx + 28, sy + 24, body)
        pyxel.rect(sx + 10, sy + 15, 8, 4, C7)
    pyxel.tri(sx + 52, sy + 14, sx + 14, sy + 38, sx + 70, sy + 20, C1)
    pyxel.tri(sx + 104, sy + 14, sx + 142, sy + 38, sx + 86, sy + 20, C1)
    pyxel.tri(sx + 62, sy + 14, sx + 28, sy - 8, sx + 78, sy + 12, C1)
    pyxel.tri(sx + 94, sy + 14, sx + 128, sy - 8, sx + 78, sy + 12, C1)
    for ex in (54, 72, 90, 108):
        pyxel.rect(sx + ex, sy + 24, 8, 5, C4)
        if self.anim % 4 < 2:
            pyxel.pset(sx + ex + 3, sy + 30, C9)
    if getattr(self, 'warning_phase', False) and not getattr(self, 'has_dropped', False):
        n = max(1, int(math.ceil(max(0, getattr(self, 'drop_cd', FPS)) / FPS)))
        _v22_bold_red_text(sx + 55, sy - 22, '!!! NUKE ' + str(n) + ' !!!', C8)
BomberEnemy.draw = _bomber_draw_v22
_old_spawn_explosion_v22 = Game.spawn_explosion
def _spawn_explosion_v22(self, x, y, power=12, kind='normal'):
    result = _old_spawn_explosion_v22(self, x, y, power, kind)
    try:
        near_ground = abs(y - self.terrain.ground_y_at(x, self.difficulty_value())) < 18
        visible = getattr(self, 'cam_x', 0) - 6 <= x <= getattr(self, 'cam_x', 0) + SCREEN_W + 6
    except Exception:
        near_ground = False
        visible = False
    if (kind == 'nuke' and near_ground and visible and getattr(self, 'state', '') == 'playing'
            and not _v20_is_multiplayer_context(self) and not _v20_boss_active(self)):
        self.nuke_aftershock_timer = max(getattr(self, 'nuke_aftershock_timer', 0), FPS * 2)
        self.nuke_aftershock_x = x
        self.nuke_aftershock_seed = random.randint(0, 999999)
    return result
Game.spawn_explosion = _spawn_explosion_v22
_old_handle_collisions_v22 = Game.handle_collisions
def _handle_collisions_v22(self):
    result = _old_handle_collisions_v22(self)
    _v22_clean_texts(self)
    return result
Game.handle_collisions = _handle_collisions_v22
_old_update_playing_v22_clean = Game.update_playing
def _update_playing_v22_clean(self):
    result = _old_update_playing_v22_clean(self)
    _v22_clean_texts(self)
    return result
Game.update_playing = _update_playing_v22_clean
_base_draw_world_v22 = globals().get('_old_draw_world_v14', Game.draw_world)
def _draw_world_v22(self):
    _base_draw_world_v22(self)
    _draw_earthquake_overlay_v22(self)
    _draw_damage_flash_v14(self)
    try:
        if getattr(self.player, 'hp', 99) <= 1 and getattr(self, 'state', '') == 'playing':
            pyxel.camera(0, 0)
            for x, y, dx, dy in ((2, 2, 18, 0), (2, 2, 0, 10), (SCREEN_W - 20, 2, 18, 0), (SCREEN_W - 2, 2, 0, 10), (2, SCREEN_H - 3, 18, 0), (2, SCREEN_H - 13, 0, 10), (SCREEN_W - 20, SCREEN_H - 3, 18, 0), (SCREEN_W - 2, SCREEN_H - 13, 0, 10)):
                pyxel.line(x, y, x + dx, y + dy, C8)
    except Exception:
        pass
    if getattr(self, 'boss_death_cinematic', 0) > 0:
        t = getattr(self, 'boss_death_cinematic', 0)
        total = max(FPS * 4, getattr(self, 'boss_death_total', FPS * 4))
        elapsed = total - t
        cx = int(getattr(self, 'boss_death_x', self.player.x) - self.cam_x + 38)
        cy = int(getattr(self, 'boss_death_y', 72) + 34)
        pyxel.camera(0, 0)
        if elapsed < 55:
            pyxel.rect(18, 16, 126, 42, C0); pyxel.rectb(18, 16, 126, 42, C7)
            pyxel.text(29, 28, 'CORE CRACKS', C10)
            pyxel.text(36, 40, '!!!', C8)
        elif elapsed < 125:
            pyxel.rect(174, 14, 128, 48, C0); pyxel.rectb(174, 14, 128, 48, C8)
            pyxel.text(188, 27, 'MISSILES DETONATE', C10)
            pyxel.text(204, 41, 'BOOM BOOM', C8)
        else:
            pyxel.rect(61, 138, 204, 26, C0); pyxel.rectb(61, 138, 204, 26, C14)
            pyxel.text(83, 147, 'BOSS DESTROYED - EXIT UNLOCKING', C14)
        r = 24 + elapsed // 2
        pyxel.circb(cx, cy, min(110, r), C8)
        pyxel.circb(cx, cy, min(124, r + 14), C10)
        pyxel.circb(cx, cy, min(140, r + 28), C7 if (pyxel.frame_count // 3) % 2 else C14)
    try:
        pyxel.camera(0, 0)
        if getattr(self, 'empire_boss_tip_pause', False):
            pyxel.rect(18, 44, 284, 88, C0)
            pyxel.rectb(18, 44, 284, 88, C10)
            pyxel.rectb(21, 47, 278, 82, C14)
            pyxel.text(48, 56, 'QUEST COMPLETE: REACH BOSS FIGHT', C10)
            pyxel.text(59, 70, '+1 HEALTH FOR THIS BOSS', C7)
            pyxel.text(37, 86, 'LASER TIP: DASH SHORTLY BEFORE IT FIRES', C14)
            pyxel.text(45, 99, 'IF TIMED RIGHT, THE LASER MISSES YOU', C7)
            pyxel.text(82, 116, 'PRESS ENTER TO RESUME', C10 if (pyxel.frame_count // 8) % 2 == 0 else C7)
        elif getattr(self, 'empire_boss_tip_resume_flash', 0) > 0:
            self.empire_boss_tip_resume_flash -= 1
            if (pyxel.frame_count // 4) % 2 == 0:
                pyxel.text(95, 38, 'SPAWN SHIELD ACTIVE', C12)
    except Exception:
        pass
Game.draw_world = _draw_world_v22
_TUTORIAL_ORDER_V22 = ['jump', 'move', 'weapon', 'balloon', 'rocket', 'bomb', 'dash', 'pause']
globals()['_TUTORIAL_ORDER_V21'] = _TUTORIAL_ORDER_V22
def _tutorial_instruction_v22(game):
    need = _current_tutorial_need_v21(game)
    return {
        'jump': 'W = JUMP. DOUBLE TAP IN AIR FOR DOUBLE JUMP.',
        'move': 'A/D = MOVE. STAY ON THE TRAINING GROUND.',
        'weapon': 'SHIFT = SWITCH: BLASTER FAST, SPREAD WIDE + SLOW.',
        'balloon': 'AIM WITH ARROWS AND SHOOT THE BALLOON.',
        'rocket': 'K = ROCKET. IT HOMES INTO ENEMIES.',
        'bomb': 'J = HEAVY BOMB. WATCH THE GROUND IMPACT.',
        'dash': 'SPACE = DASH THROUGH DANGER.',
        'pause': 'P = PAUSE. PRESS ENTER TO RESUME.',
    }.get(need, 'TRAINING')
globals()['_tutorial_instruction_v21'] = _tutorial_instruction_v22
_old_update_tutorial_v22 = Game.update_tutorial
def _update_tutorial_v22(self):
    result = _old_update_tutorial_v22(self)
    _v22_clean_texts(self)
    return result
Game.update_tutorial = _update_tutorial_v22
def _draw_training_map_v22(game):
    pyxel.cls(C0)
    for i in range(0, SCREEN_W, 18):
        h = 18 + (i * 7) % 34
        pyxel.rect(i, 132 - h, 10, h, C5)
        if i % 36 == 0:
            pyxel.pset(i + 5, 130 - h, C8)
    game.terrain.draw(0, game.difficulty_value())
    pyxel.text(8, 6, _tutorial_instruction_v22(game), C7)
    pyxel.text(8, 16, 'ENTER SKIP', C6)
    pyxel.text(8, 28, 'UNLIMITED ROCKETS + BOMBS HERE', C10)
    labels = [('W JUMP','jump'),('A/D MOVE','move'),('SHIFT WEAPON','weapon'),('ARROWS BALLOON','balloon'),('K ROCKET DRONE','rocket'),('J HEAVY BOMB','bomb'),('SPACE DASH','dash'),('P PAUSE','pause')]
    y = 42
    for idx, (label, flag) in enumerate(labels):
        col = C10 if game.tutorial_flags.get(flag) else (C14 if idx == game.tutorial_step else C5)
        pyxel.text(8, y, ('OK ' if game.tutorial_flags.get(flag) else '-- ') + label, col)
        y += 8
    need = _current_tutorial_need_v21(game)
    intro = clamp(getattr(game, 'tutorial_target_intro', 0) / 28, 0, 1)
    if need == 'balloon' and game.tutorial_balloon.get('alive'):
        bal = game.tutorial_balloon; bx = int(bal['x']); by = int(-24 + (bal['y'] + 24) * intro)
        pyxel.circ(bx, by, 8, C8); pyxel.circ(bx - 2, by - 2, 3, C14); pyxel.line(bx, by + 8, bx, by + 22, C7)
        pyxel.text(bx - 31, by - 20, 'POP BALLOON', C14)
    elif need == 'rocket' and game.tutorial_drone.get('alive'):
        dr = game.tutorial_drone; dx = int(SCREEN_W + 30 - (SCREEN_W + 30 - dr['x']) * intro); dy = dr['y']
        pyxel.rect(dx - 12, dy - 5, 24, 10, C5); pyxel.rectb(dx - 12, dy - 5, 24, 10, C12)
        pyxel.circ(dx - 16, dy, 3, C7); pyxel.circ(dx + 16, dy, 3, C7)
        pyxel.text(168, 48, 'K ROCKET HOMES', C10)
    elif need == 'bomb' and game.tutorial_turret.get('alive'):
        tu = game.tutorial_turret; tx = tu['x']; ground = game.terrain.ground_y_at(tx, game.difficulty_value())
        rise = int(24 * intro); ty = ground - 10 + (24 - rise)
        pyxel.rect(tx - 8, ty, 16, 10, C4); pyxel.rectb(tx - 8, ty, 16, 10, C8); pyxel.line(tx, ty, tx + 12, ty - 8, C7)
        pyxel.text(164, 126, 'J HEAVY BOMB', C8)
    elif need == 'weapon':
        pyxel.rect(176, 54, 110, 35, C0); pyxel.rectb(176, 54, 110, 35, C14)
        pyxel.text(182, 60, 'BLASTER: FAST STRAIGHT', C10)
        pyxel.text(182, 69, 'SPREAD: WIDE SLOWER', C15)
        pyxel.text(182, 78, 'NO PLAYER LASER GUN', C8)
    pyxel.rect(120, 4, 192, 24, C0)
    pyxel.rectb(120, 4, 192, 24, C6)
    pyxel.text(126, 9, 'POWERUPS: RAPID / SHIELD / SPREAD', C10)
    pyxel.text(126, 18, 'ROCKET+BOMB AMMO / OVERDRIVE', C7)
    for obj_list in (game.player_bullets, game.rockets, game.bombs, game.effects, game.particles, game.texts):
        for o in obj_list:
            if hasattr(o, 'draw'):
                o.draw(0)
    game.player.draw(0)
globals()['_draw_training_map_v21'] = _draw_training_map_v22
globals()['_draw_training_map_v15'] = _draw_training_map_v22
globals()['_draw_training_map_v13'] = _draw_training_map_v22
def _draw_tutorial_v22(self):
    if not hasattr(self, 'tutorial_flags') or 'weapon' not in getattr(self, 'tutorial_flags', {}):
        _setup_tutorial_v21(self)
    _draw_training_map_v22(self)
Game.draw_tutorial = _draw_tutorial_v22

FINAL_BUILD_TAG = 'v24-mp-fair-quake-midlevel-fast-clean-escargot'

# ---------------- V24: fairness / quake performance / tutorial clarity ----------------

def _v24_flash_text(x, y, msg, col=None):
    col = col if col is not None else (C8 if (pyxel.frame_count // 8) % 2 == 0 else C10)
    pyxel.text(int(x) + 1, int(y) + 1, str(msg), C0)
    pyxel.text(int(x), int(y), str(msg), col)

def _v24_center_text(msg, y, col=None):
    x = max(4, (SCREEN_W - len(str(msg)) * 4) // 2)
    _v24_flash_text(x, y, msg, col)

def _v24_prune_terrain(game, softer=False):
    try:
        cx = getattr(game, 'cam_x', 0) + SCREEN_W // 2
        craters = list(getattr(game.terrain, 'craters', []))
        uplifts = list(getattr(game.terrain, 'uplifts', []))
        crater_cap = 58 if softer else 74
        uplift_cap = 62 if softer else 82
        if len(craters) > crater_cap:
            craters.sort(key=lambda c: abs(c.get('x', 0) - cx))
            game.terrain.craters = sorted(craters[:crater_cap], key=lambda c: c.get('x', 0))
        if len(uplifts) > uplift_cap:
            uplifts.sort(key=lambda u: abs(u.get('x', 0) - cx))
            game.terrain.uplifts = sorted(uplifts[:uplift_cap], key=lambda u: u.get('x', 0))
    except Exception:
        pass

def _v24_add_quake_trench(terrain, x, rng):
    terrain.craters.append({
        'x': x,
        'r': rng.randint(20, 31),
        'gap_r': rng.randint(7, 11),
        'depth': rng.randint(19, 34),
    })
    if len(terrain.craters) > 76:
        terrain.craters.pop(0)

def _v24_add_quake_uplift(terrain, x, rng):
    if not hasattr(terrain, 'uplifts'):
        terrain.uplifts = []
    terrain.uplifts.append({
        'x': x,
        'r': rng.randint(42, 88),
        'height': rng.randint(10, 24),
    })
    if len(terrain.uplifts) > 84:
        terrain.uplifts.pop(0)

globals()['_v19_add_quake_trench'] = _v24_add_quake_trench
globals()['_v19_add_quake_uplift'] = _v24_add_quake_uplift
globals()['_v22_add_quake_trench'] = _v24_add_quake_trench
globals()['_v22_add_quake_uplift'] = _v24_add_quake_uplift

_old_ground_v24_cap = Terrain.ground_y_at
def _terrain_ground_y_at_v24_cap(self, x, difficulty):
    y = _old_ground_v24_cap(self, x, difficulty)
    try:
        for u in getattr(self, 'uplifts', []):
            if abs(x - u.get('x', 0)) < max(1, u.get('r', 1)):
                return max(y, 96)
    except Exception:
        pass
    return y
Terrain.ground_y_at = _terrain_ground_y_at_v24_cap

def _v24_mark_crack(game, x, rng):
    try:
        gy = game.terrain.ground_y_at(x, game.difficulty_value())
        game.texts.append(FlashText(x - 3, gy - rng.randint(22, 34), '!', C8 if rng.random() < .55 else C10, rng.randint(22, 36)))
    except Exception:
        pass

def _v24_shape_quake_span(game, start, end, seed):
    rng = random.Random(seed)
    x = start + rng.randint(70, 145)
    last_trench = -999999
    while x < end:
        x += rng.randint(86, 150)
        if x >= end:
            break
        if rng.random() < 0.76 and abs(x - last_trench) > 104:
            _v24_add_quake_trench(game.terrain, x, rng)
            _v24_mark_crack(game, x, rng)
            last_trench = x
            if rng.random() < 0.78:
                _v24_add_quake_uplift(game.terrain, x + rng.choice([-1, 1]) * rng.randint(36, 78), rng)
        else:
            _v24_add_quake_uplift(game.terrain, x + rng.randint(-48, 62), rng)
        if rng.random() < 0.66:
            _v24_add_quake_uplift(game.terrain, x + rng.randint(-70, 82), rng)
    _v24_prune_terrain(game, softer=False)

def _v19_shape_whole_quake_map(game):
    if '_v20_quake_state' in globals():
        _v20_quake_state(game)
    if _v20_boss_active(game) or not getattr(game, 'v19_quake_level', False) or getattr(game, 'v19_quake_shaped', False):
        return
    try:
        length = _v19_level_len(game)
    except Exception:
        length = LEVEL_DISTANCE
    start = int(getattr(game, 'checkpoint_x', 40)) + max(280, int(length * 0.24))
    end = int(getattr(game, 'checkpoint_x', 40)) + length - 90
    _v24_shape_quake_span(game, start, end, int(getattr(game, 'v19_quake_seed', 0)) + int(getattr(game, 'level', 1)) * 2371)
    game.v19_quake_shaped = True
    game.v19_quake_active_timer = FPS * 8
    game.add_shake(10, 4)

globals()['_v19_shape_whole_quake_map'] = _v19_shape_whole_quake_map

def _v20_shape_survival_quake_map(game):
    if '_v20_quake_state' in globals():
        _v20_quake_state(game)
    if _v20_boss_active(game) or getattr(game, 'v19_quake_shaped', False):
        return
    game.v20_survival_quake_serial = getattr(game, 'v20_survival_quake_serial', 0) + 1
    start = int(game.cam_x + SCREEN_W + 160)
    end = start + 2300
    _v24_shape_quake_span(game, start, end, int(getattr(game, 'v19_quake_seed', 0)) + game.v20_survival_quake_serial * 2111)
    game.v19_quake_shaped = True
    game.v19_quake_active_timer = FPS * 8
    game.add_shake(10, 4)

globals()['_v20_shape_survival_quake_map'] = _v20_shape_survival_quake_map

def _v19_prepare_quake_level(game):
    if '_v20_quake_state' in globals():
        _v20_quake_state(game)
    if _v20_boss_active(game) or not _v19_campaign_like(game):
        game.v19_quake_level = False
        game.v19_quake_prepared_level = None
        return
    lvl = int(getattr(game, 'level', 1))
    if lvl <= 1:
        game.v19_quake_level = False
        game.v19_quake_prepared_level = lvl
        game.v19_quake_warning_timer = 0
        game.v19_quake_active_timer = 0
        return
    if getattr(game, 'v19_quake_prepared_level', None) == lvl:
        return
    game.v19_quake_prepared_level = lvl
    game.v19_quake_warning_timer = 0
    game.v19_quake_active_timer = 0
    game.v19_quake_shaped = False
    game.v19_quake_impact_pending = False
    game.v24_quake_mid_warning_started = False
    since = lvl - int(getattr(game, 'v19_last_quake_level', 0))
    if since >= 2 or random.random() < 0.62:
        game.v19_quake_level = True
        game.v19_last_quake_level = lvl
        game.v19_quake_seed = random.randint(1000, 999999)
    else:
        game.v19_quake_level = False

globals()['_v19_prepare_quake_level'] = _v19_prepare_quake_level

def _v22_start_survival_quake(game):
    if '_v20_quake_state' in globals():
        _v20_quake_state(game)
    if _v20_boss_active(game) or _v20_is_multiplayer_context(game):
        return
    game.v19_quake_level = True
    game.v19_quake_shaped = False
    game.v19_quake_seed = random.randint(1000, 999999)
    game.v19_quake_warning_timer = FPS * 2
    game.v19_quake_active_timer = 0
    game.v19_quake_impact_pending = True
    game.v24_quake_mid_warning_started = True

globals()['_v22_start_survival_quake'] = _v22_start_survival_quake

_base_spawn_explosion_v24 = globals().get('_old_spawn_explosion_v21', Game.spawn_explosion)
def _spawn_explosion_v24(self, x, y, power=12, kind='normal'):
    result = _base_spawn_explosion_v24(self, x, y, power, kind)
    try:
        near_ground = abs(y - self.terrain.ground_y_at(x, self.difficulty_value())) < 18
        visible = getattr(self, 'cam_x', 0) + 6 <= x <= getattr(self, 'cam_x', 0) + SCREEN_W - 6
    except Exception:
        near_ground = False; visible = False
    if (kind == 'nuke' and near_ground and visible and getattr(self, 'state', '') == 'playing'
            and not _v20_is_multiplayer_context(self) and not _v20_boss_active(self)):
        self.nuke_aftershock_timer = FPS * 2
        self.nuke_aftershock_x = x
        self.nuke_aftershock_seed = random.randint(0, 999999)
    return result
Game.spawn_explosion = _spawn_explosion_v24

def _update_earthquake_v24(game):
    if '_v20_quake_state' in globals():
        _v20_quake_state(game)
    if getattr(game, 'state', '') != 'playing' or _v20_is_multiplayer_context(game) or _v20_boss_active(game):
        game.earthquake_timer = 0
        game.earthquake_warning_timer = 0
        game.quake_red_border_timer = 0
        game.v19_quake_warning_timer = 0
        game.v19_quake_active_timer = 0
        game.v19_quake_impact_pending = False
        if _v20_boss_active(game):
            game.nuke_aftershock_timer = 0
        return
    game.earthquake_timer = 0
    game.earthquake_warning_timer = 0
    game.quake_red_border_timer = 0
    if getattr(game, 'nuke_aftershock_timer', 0) > 0:
        game.nuke_aftershock_timer -= 1
        if pyxel.frame_count % 3 == 0:
            game.add_shake(2, random.randint(1, 3))
        if game.nuke_aftershock_timer % 45 == 0:
            rng = random.Random(getattr(game, 'nuke_aftershock_seed', 0) + game.nuke_aftershock_timer)
            base_x = getattr(game, 'nuke_aftershock_x', game.player.x + 140)
            for _ in range(2):
                tx = base_x + rng.randint(-115, 150)
                if rng.random() < 0.45:
                    _v24_add_quake_trench(game.terrain, tx, rng); _v24_mark_crack(game, tx, rng)
                else:
                    _v24_add_quake_uplift(game.terrain, tx, rng)
            _v24_prune_terrain(game, softer=True)
    try:
        is_surv = game.is_survival()
    except Exception:
        is_surv = False
    if is_surv:
        px = max(getattr(game.player, 'x', 0), getattr(game, 'total_distance', 0))
        if (not getattr(game, 'v19_quake_level', False)
                and px >= getattr(game, 'v20_survival_quake_next_x', 1600)):
            _v22_start_survival_quake(game)
            game.v20_survival_quake_next_x = px + random.randint(2000, 2800)
    else:
        _v19_prepare_quake_level(game)
        if getattr(game, 'v19_quake_level', False) and not getattr(game, 'v24_quake_mid_warning_started', False):
            try:
                ratio = game.distance / max(1, _v19_level_len(game))
            except Exception:
                ratio = 0
            if ratio >= 0.40:
                game.v24_quake_mid_warning_started = True
                game.v19_quake_warning_timer = FPS * 2
                game.v19_quake_impact_pending = True
    if not getattr(game, 'v19_quake_level', False):
        if pyxel.frame_count % 90 == 0:
            _v24_prune_terrain(game, softer=True)
        return
    if getattr(game, 'v19_quake_warning_timer', 0) > 0 or getattr(game, 'v19_quake_active_timer', 0) > 0:
        game.field_order = None
        game.field_order_progress = 0
        game.field_order_cooldown = max(getattr(game, 'field_order_cooldown', 0), FPS * 6)
    if getattr(game, 'v19_quake_warning_timer', 0) > 0:
        game.v19_quake_warning_timer -= 1
        if pyxel.frame_count % 4 == 0:
            game.add_shake(2, random.randint(1, 3))
        if game.v19_quake_warning_timer <= 0 and getattr(game, 'v19_quake_impact_pending', False):
            game.v19_quake_impact_pending = False
            if is_surv:
                _v20_shape_survival_quake_map(game)
            else:
                _v19_shape_whole_quake_map(game)
        return
    if getattr(game, 'v19_quake_active_timer', 0) > 0:
        game.v19_quake_active_timer -= 1
        game.add_shake(2, random.randint(2, 5))
        if game.v19_quake_active_timer % 38 == 0:
            rng = random.Random(game.v19_quake_active_timer + int(game.player.x))
            tx = game.player.x + rng.randint(80, 290)
            _v24_mark_crack(game, tx, rng)
        if game.v19_quake_active_timer <= 0:
            if is_surv:
                game.v19_quake_level = False
            _v24_prune_terrain(game, softer=True)
        return

globals()['_update_earthquake_v14'] = _update_earthquake_v24

def _draw_earthquake_overlay_v24(game):
    if _v20_is_multiplayer_context(game) or _v20_boss_active(game):
        return
    pyxel.camera(0, 0)
    flash = C8 if (pyxel.frame_count // 8) % 2 == 0 else C10
    if getattr(game, 'v19_quake_level', False) and getattr(game, 'v19_quake_warning_timer', 0) > 0:
        sec = max(1, (game.v19_quake_warning_timer + FPS - 1) // FPS)
        _v24_center_text('SEISMIC WAVES IN ' + str(sec) + '!', 74, flash)
        _v24_center_text('WATCH FOR ! TRENCH BREAKS', 88, C8 if (pyxel.frame_count // 12) % 2 == 0 else C10)
    elif getattr(game, 'v19_quake_level', False) and getattr(game, 'v19_quake_active_timer', 0) > 0:
        _v24_center_text('EARTHQUAKE!', 9, flash)
        if (pyxel.frame_count // 20) % 2 == 0:
            _v24_center_text('! = NEW TRENCH', 21, C8)
    if getattr(game, 'nuke_aftershock_timer', 0) > 0:
        _v24_center_text('AFTERSHOCK!', 82, flash)
        _v24_center_text('!', 95, flash)

globals()['_draw_earthquake_overlay_v14'] = _draw_earthquake_overlay_v24
globals()['_draw_earthquake_overlay_v22'] = _draw_earthquake_overlay_v24
globals()['_draw_earthquake_overlay_v21'] = _draw_earthquake_overlay_v24

def _bomber_spawn_chance_v24(self):
    if _v20_boss_active(self) or _v20_is_multiplayer_context(self):
        return 0
    d = self.difficulty_value()
    if d < 2.1:
        return 0
    quake_soften = 0.62 if (getattr(self, 'v19_quake_active_timer', 0) > 0 or getattr(self, 'v19_quake_warning_timer', 0) > 0) else 1.0
    empire_soften = 0.93 if getattr(self, 'empire_mode', False) else 1.0
    return min(0.00095 + d * 0.00024, 0.0084) * quake_soften * empire_soften
Game.bomber_spawn_chance = _bomber_spawn_chance_v24

def _bomber_draw_v24(self, cam_x):
    sx = int(self.x - cam_x); sy = int(self.y)
    body = C7 if self.flash > 0 else C12
    pyxel.rect(sx + 28, sy + 12, 100, 12, body); pyxel.rect(sx + 20, sy + 14, 116, 8, C6)
    if self.dir == 1:
        pyxel.tri(sx + 128, sy + 12, sx + 154, sy + 18, sx + 128, sy + 24, body); pyxel.rect(sx + 136, sy + 15, 8, 4, C7)
    else:
        pyxel.tri(sx + 28, sy + 12, sx + 2, sy + 18, sx + 28, sy + 24, body); pyxel.rect(sx + 10, sy + 15, 8, 4, C7)
    pyxel.tri(sx + 52, sy + 14, sx + 14, sy + 38, sx + 70, sy + 20, C1)
    pyxel.tri(sx + 104, sy + 14, sx + 142, sy + 38, sx + 86, sy + 20, C1)
    pyxel.tri(sx + 62, sy + 14, sx + 28, sy - 8, sx + 78, sy + 12, C1)
    pyxel.tri(sx + 94, sy + 14, sx + 128, sy - 8, sx + 78, sy + 12, C1)
    for ex in (54, 72, 90, 108):
        pyxel.rect(sx + ex, sy + 24, 8, 5, C4)
        if self.anim % 4 < 2:
            pyxel.pset(sx + ex + 3, sy + 30, C9)
    if getattr(self, 'warning_phase', False) and not getattr(self, 'has_dropped', False):
        n = max(1, int(math.ceil(max(0, getattr(self, 'drop_cd', FPS)) / FPS)))
        _v24_flash_text(sx + 55, sy - 22, '!!! NUKE ' + str(n) + ' !!!')
BomberEnemy.draw = _bomber_draw_v24

_old_player_update_v24_unstuck = Player.update
def _player_update_v24_unstuck(self, difficulty, dev_mode=False):
    if getattr(self, 'stun_timer', 0) > FPS * 2:
        self.stun_timer = FPS * 2
    return _old_player_update_v24_unstuck(self, difficulty, dev_mode)
Player.update = _player_update_v24_unstuck

_old_game_update_v24_safety = Game.update
def _update_v24_safety(self):
    try:
        if getattr(self, 'state', '') != 'playing' and hasattr(self, 'player'):
            self.player.stun_timer = 0
        if hasattr(self, 'player') and getattr(self.player, 'stun_timer', 0) > FPS * 2:
            self.player.stun_timer = FPS * 2
    except Exception:
        pass
    return _old_game_update_v24_safety(self)
Game.update = _update_v24_safety

_old_update_playing_v24_prune = Game.update_playing
def _update_playing_v24_prune(self):
    result = _old_update_playing_v24_prune(self)
    if pyxel.frame_count % 60 == 0 and getattr(self, 'v19_quake_active_timer', 0) <= 0:
        _v24_prune_terrain(self, softer=True)
    return result
Game.update_playing = _update_playing_v24_prune

_old_reset_run_v24 = Game.reset_run
def _reset_run_v24(self):
    result = _old_reset_run_v24(self)
    self.v19_last_quake_level = max(1, int(getattr(self, 'v19_last_quake_level', 0)))
    self.v24_quake_mid_warning_started = False
    return result
Game.reset_run = _reset_run_v24

# ---------------- V24: French escargot bullets ----------------
_old_style_key_v24 = globals().get('_v16_bullet_style_for_key')
def _v16_bullet_style_for_key(key):
    if key == 'france':
        return 'escargot'
    if _old_style_key_v24:
        return _old_style_key_v24(key)
    return 'empire_flag_bolt'
globals()['_v16_bullet_style_for_key'] = _v16_bullet_style_for_key
_old_empire_style_v24 = globals().get('_empire_bullet_style_v15')
def _empire_bullet_style_v15(key):
    if key == 'france':
        return 'escargot'
    if _old_empire_style_v24:
        return _old_empire_style_v24(key)
    return 'empire_flag_bolt'
globals()['_empire_bullet_style_v15'] = _empire_bullet_style_v15
_old_bullet_draw_v24 = Bullet.draw
def _bullet_draw_v24(self, cam_x):
    if getattr(self, 'empire_style', None) != 'escargot':
        return _old_bullet_draw_v24(self, cam_x)
    sx, sy = int(self.x - cam_x), int(self.y)
    if sx < -40 or sx > SCREEN_W + 40 or sy < -30 or sy > SCREEN_H + 30:
        return
    facing = 1 if getattr(self, 'vx', 1) >= 0 else -1
    pyxel.circ(sx - facing * 3, sy, 6, C4)
    pyxel.circb(sx - facing * 3, sy, 6, C10)
    pyxel.circb(sx - facing * 3, sy, 3, C7)
    pyxel.rect(sx - facing * 1, sy + 3, 11 * facing, 3, C5)
    pyxel.line(sx + facing * 6, sy + 2, sx + facing * 11, sy - 3, C10)
    pyxel.line(sx + facing * 7, sy + 2, sx + facing * 12, sy - 1, C10)
    pyxel.pset(sx + facing * 11, sy - 3, C7)
    pyxel.pset(sx + facing * 12, sy - 1, C7)
Bullet.draw = _bullet_draw_v24
_old_mini_v24 = globals().get('_draw_mini_bullet_thing_v16')
def _draw_mini_bullet_thing_v16(style, x, y, seed):
    if style == 'escargot':
        pyxel.circ(x - 2, y, 4, C4)
        pyxel.circb(x - 2, y, 4, C10)
        pyxel.rect(x + 1, y + 2, 7, 2, C5)
        pyxel.line(x + 5, y + 1, x + 8, y - 2, C10)
        pyxel.pset(x + 8, y - 2, C7)
        return
    if _old_mini_v24:
        return _old_mini_v24(style, x, y, seed)
globals()['_draw_mini_bullet_thing_v16'] = _draw_mini_bullet_thing_v16

# ---------------- V24: multiplayer fairness and clearer standalone interface ----------------
def _mp24_shared_camera(game):
    live = [p for p in getattr(game, 'mp_arena_players', []) if p.get('alive')]
    if not live:
        return
    avg_x = sum(p['x'] for p in live) / len(live)
    leader = max(p['x'] for p in live)
    target = max(0, max(avg_x - SCREEN_W * 0.48, leader - SCREEN_W + 74))
    game.cam_x += (target - game.cam_x) * 0.18
    left = game.cam_x + 12
    right = game.cam_x + SCREEN_W - 28
    for p in live:
        old = p['x']
        p['x'] = clamp(p['x'], left, right)
        if p['x'] != old:
            p['vx'] = 0

def _mp19_start_round(game):
    count = int(clamp(getattr(game, 'mp_player_count', 2), 2, 3))
    game.terrain.reset()
    game.mp_arena_players = []
    game.mp_arena_bullets = []
    game.mp_arena_hazards = []
    game.mp_arena_spawn_cd = 46
    game.mp_arena_timer = 0
    game.cam_x = 0
    game.shake_timer = 0
    game.effects.clear(); game.particles.clear(); game.texts.clear()
    game.field_order = None
    game.v19_quake_level = False
    game.nuke_aftershock_timer = 0
    for i in range(count):
        x = 58 + i * 30
        y = game.terrain.ground_y_at(x + 8, _mp20_difficulty(game)) - PLAYER_H
        empire = i in getattr(game, 'mp_empire_players', set())
        game.mp_arena_players.append({
            'idx': i, 'x': x, 'y': y, 'vx': 0.0, 'vy': 0.0, 'facing': 1,
            'hp': 3, 'alive': True, 'dist': 0, 'survive_frames': 0, 'invuln': 90, 'shot_cd': 0,
            'empire': empire, 'on_ground': True, 'dead_timer': 0, 'walk': 0,
        })
    game.mp_scores = [0 for _ in range(count)]
    game.state = 'mp_arena'
    stop_story_music()
globals()['_mp19_start_round'] = _mp19_start_round

def _mp19_update_player(game, p):
    keys = _MP19_KEYS[p['idx']]
    if not p.get('alive'):
        p['dead_timer'] = max(0, p.get('dead_timer', 0) - 1)
        return
    p['survive_frames'] = p.get('survive_frames', 0) + 1
    if p['invuln'] > 0:
        p['invuln'] -= 1
    if p['shot_cd'] > 0:
        p['shot_cd'] -= 1
    speed = 1.88
    jump = -5.45
    vx = 0
    if pyxel.btn(keys['left']):
        vx -= speed; p['facing'] = -1
    if pyxel.btn(keys['right']):
        vx += speed; p['facing'] = 1
    p['vx'] = vx
    if pyxel.btnp(keys['jump']) and p.get('on_ground'):
        p['vy'] = jump; p['on_ground'] = False
    if pyxel.btn(keys['down']) and not p.get('on_ground'):
        p['vy'] += 0.35
    p['vy'] = min(p['vy'] + GRAVITY, MAX_FALL)
    p['x'] += p['vx']; p['y'] += p['vy']
    p['x'] = max(0, p['x'])
    foots = (p['x'] + 3, p['x'] + 8, p['x'] + 13)
    grounds = [game.terrain.ground_y_at(f, _mp20_difficulty(game)) for f in foots]
    target_ground = min(grounds)
    if p['y'] >= target_ground - PLAYER_H:
        p['y'] = target_ground - PLAYER_H; p['vy'] = 0; p['on_ground'] = True
    else:
        p['on_ground'] = False
    if p['y'] > SCREEN_H + 44:
        _mp19_player_hit(game, p, 3)
    p['walk'] = p.get('walk', 0) + 1 if abs(p['vx']) > 0.1 and p.get('on_ground') else 0
    p['dist'] = int(p.get('survive_frames', 0) * 0.78)
    if pyxel.btnp(keys['shoot']) and p['shot_cd'] <= 0:
        p['shot_cd'] = 15
        game.mp_arena_bullets.append({
            'x': p['x'] + 8 + p['facing'] * 7, 'y': p['y'] + 11,
            'vx': p['facing'] * 6.4, 'vy': 0, 'owner': p['idx'], 'life': 76,
            'style': None, 'seed': random.randint(0, 9999)
        })
        sfx(0, 0)
globals()['_mp19_update_player'] = _mp19_update_player

def _mp24_spawn_hazard(game):
    left = int(game.cam_x + 18)
    right = int(game.cam_x + SCREEN_W - 22)
    kind = random.choices(['missile', 'bomb', 'drone', 'turret', 'mine'], [34, 25, 20, 12, 9])[0]
    live = [p for p in getattr(game, 'mp_arena_players', []) if p.get('alive')]
    if live and random.random() < 0.50:
        target = random.choice(live)
        x = int(clamp(target['x'] + random.randint(-55, 70), left, right))
    else:
        x = random.randint(left, right)
    if kind == 'missile':
        game.mp_arena_hazards.append({'kind': 'missile', 'x': x, 'y': -16, 'vy': random.uniform(2.0, 3.35), 'alive': True})
    elif kind == 'bomb':
        game.mp_arena_hazards.append({'kind': 'bomb', 'x': x, 'y': -12, 'vx': random.uniform(-0.35, 0.35), 'vy': random.uniform(1.8, 2.7), 'alive': True})
    elif kind == 'drone':
        side = random.choice([-1, 1])
        y = random.randint(58, 116)
        game.mp_arena_hazards.append({'kind': 'drone', 'x': game.cam_x + (-24 if side > 0 else SCREEN_W + 24), 'y': y, 'vx': side * random.uniform(1.2, 1.9), 'alive': True})
    elif kind == 'turret':
        gy = game.terrain.ground_y_at(x, _mp20_difficulty(game))
        game.mp_arena_hazards.append({'kind': 'turret', 'x': x, 'y': gy - 12, 'timer': random.randint(180, 260), 'alive': True})
    else:
        gy = game.terrain.ground_y_at(x, _mp20_difficulty(game))
        game.mp_arena_hazards.append({'kind': 'mine', 'x': x, 'y': gy - 5, 'timer': random.randint(115, 190), 'alive': True})

def _mp24_hazard_box(h):
    k = h.get('kind')
    if k == 'drone': return h['x'] - 10, h['y'] - 5, 20, 10
    if k == 'turret': return h['x'] - 9, h['y'] - 4, 18, 16
    if k == 'bomb': return h['x'] - 5, h['y'] - 5, 10, 10
    return h['x'] - 5, h['y'] - 5, 10, 10

def _mp19_update_bullets_and_hazards(game):
    for b in game.mp_arena_bullets:
        b['x'] += b['vx']; b['y'] += b.get('vy', 0); b['life'] -= 1
    game.mp_arena_spawn_cd -= 1
    if game.mp_arena_spawn_cd <= 0:
        game.mp_arena_spawn_cd = max(24, 58 - game.mp_arena_timer // 420)
        _mp24_spawn_hazard(game)
        if random.random() < 0.18:
            _mp24_spawn_hazard(game)
    for h in game.mp_arena_hazards:
        if not h.get('alive'):
            continue
        k = h.get('kind')
        if k == 'missile':
            h['y'] += h['vy']
            gy = game.terrain.ground_y_at(h['x'], _mp20_difficulty(game))
            if h['y'] >= gy:
                h['alive'] = False; game.spawn_explosion(h['x'], gy, 13, 'normal'); game.add_shake(4, 3)
        elif k == 'bomb':
            h['vy'] += 0.10; h['x'] += h.get('vx', 0); h['y'] += h['vy']
            gy = game.terrain.ground_y_at(h['x'], _mp20_difficulty(game))
            if h['y'] >= gy:
                h['alive'] = False; game.spawn_explosion(h['x'], gy, 16, 'normal'); game.add_shake(5, 3)
        elif k == 'drone':
            h['x'] += h['vx']; h['y'] += math.sin((pyxel.frame_count + int(h['x'])) * .08) * .25
        elif k == 'turret':
            h['timer'] -= 1
            h['y'] = game.terrain.ground_y_at(h['x'], _mp20_difficulty(game)) - 12
            if h['timer'] <= 0:
                h['alive'] = False
        elif k == 'mine':
            h['timer'] -= 1
            if h['timer'] <= 0:
                h['alive'] = False; game.spawn_explosion(h['x'], h['y'], 15, 'normal')
    for b in game.mp_arena_bullets:
        if b['life'] <= 0:
            continue
        for h in game.mp_arena_hazards:
            if not h.get('alive'):
                continue
            hx, hy, hw, hh = _mp24_hazard_box(h)
            if rects_overlap(b['x'] - 2, b['y'] - 2, 4, 4, hx, hy, hw, hh):
                h['alive'] = False; b['life'] = 0
                game.spawn_explosion(h['x'], h['y'], 12, 'normal')
                break
    for h in game.mp_arena_hazards:
        if not h.get('alive'):
            continue
        hx, hy, hw, hh = _mp24_hazard_box(h)
        for p in game.mp_arena_players:
            if p.get('alive') and rects_overlap(p['x'] + 3, p['y'] + 3, 10, 18, hx, hy, hw, hh):
                h['alive'] = False
                _mp19_player_hit(game, p, 1)
                break
    game.mp_arena_bullets = [b for b in game.mp_arena_bullets if b['life'] > 0 and game.cam_x - 70 < b['x'] < game.cam_x + SCREEN_W + 90]
    game.mp_arena_hazards = [h for h in game.mp_arena_hazards if h.get('alive') and game.cam_x - 80 < h['x'] < game.cam_x + SCREEN_W + 80 and h.get('y', 0) < SCREEN_H + 46]
globals()['_mp19_update_bullets_and_hazards'] = _mp19_update_bullets_and_hazards

def _update_mp_arena_v19(self):
    self.mp_arena_timer = getattr(self, 'mp_arena_timer', 0) + 1
    self.field_order = None; self.v19_quake_level = False; self.nuke_aftershock_timer = 0
    for p in list(getattr(self, 'mp_arena_players', [])):
        _mp19_update_player(self, p)
    _mp24_shared_camera(self)
    _mp19_update_bullets_and_hazards(self)
    _mp24_shared_camera(self)
    for e in self.effects: e.update()
    for pa in self.particles: pa.update()
    self.effects = [e for e in self.effects if e.alive]
    self.particles = [p for p in self.particles if p.alive]
    self.update_shake()
    live_count = sum(1 for p in self.mp_arena_players if p.get('alive'))
    if live_count <= 1 and self.mp_arena_timer > FPS * 4:
        _mp19_finish_round(self)
    elif self.mp_arena_timer > FPS * 90:
        _mp19_finish_round(self)
globals()['_update_mp_arena_v19'] = _update_mp_arena_v19

def _mp19_finish_round(game):
    players = getattr(game, 'mp_arena_players', [])
    if not players:
        return
    winner_p = max(players, key=lambda p: (p.get('survive_frames', 0), p.get('hp', 0), -p.get('idx', 0)))
    winner = winner_p['idx']
    game.mp_round_winner = winner
    game.mp_scores = [int(p.get('survive_frames', 0) * .78) for p in players]
    already_empire = winner in getattr(game, 'mp_empire_players', set())
    if getattr(game, 'mp_round', 1) == 1:
        game.mp_empire_players.add(winner)
        game.mp_round_message = 'P' + str(winner + 1) + ' GETS THE CROWN: EMPIRE MODE'
        game.state = 'mp_round_result'
    elif getattr(game, 'mp_round', 1) == 2:
        if already_empire:
            game.mp_match_winner = winner; game.state = 'mp_final'
        else:
            game.mp_empire_players.add(winner)
            game.mp_round_message = 'P' + str(winner + 1) + ' ALSO GETS EMPIRE: FINAL ROUND'
            game.state = 'mp_round_result'
    else:
        game.mp_match_winner = winner; game.state = 'mp_final'
    play_story_music()
globals()['_mp19_finish_round'] = _mp19_finish_round

def _mp24_draw_crown(x, y, col=C10):
    pyxel.tri(x, y + 6, x + 4, y, x + 8, y + 6, col)
    pyxel.tri(x + 6, y + 6, x + 10, y, x + 14, y + 6, col)
    pyxel.rect(x, y + 6, 14, 3, col)
    pyxel.pset(x + 4, y + 2, C7); pyxel.pset(x + 10, y + 2, C7)

def _draw_mp_select_count_v19(self):
    pyxel.cls(C0)
    pyxel.rect(22, 18, 276, 164, C0); pyxel.rectb(22, 18, 276, 164, C12)
    pyxel.text(82, 34, 'MULTIPLAYER SETUP', C14)
    pyxel.text(56, 58, 'CHOOSE HOW MANY NORMAL AVATARS JOIN', C7)
    for n in (2, 3):
        x = 106 + (n - 2) * 72
        selected = int(getattr(self, 'mp_player_count', 2)) == n
        pyxel.rect(x, 88, 50, 34, C12 if selected else C0)
        pyxel.rectb(x, 88, 50, 34, C7)
        pyxel.text(x + 22, 101, str(n), C0 if selected else C7)
    pyxel.text(56, 144, 'LEFT/RIGHT SELECT    ENTER TUTORIAL', C10)

globals()['_draw_mp_select_count_v19'] = _draw_mp_select_count_v19

def _draw_mp_tutorial_v19(self):
    pyxel.cls(C0)
    pyxel.rect(14, 10, 292, 180, C0); pyxel.rectb(14, 10, 292, 180, C7)
    pyxel.text(78, 24, 'MULTIPLAYER TUTORIAL', C14)
    pyxel.text(34, 42, 'ONE SHARED SCREEN. NO QUESTS. NO EARTHQUAKES.', C7)
    rows = [_MP19_KEYS[0]['label'], _MP19_KEYS[1]['label']]
    if int(getattr(self, 'mp_player_count', 2)) >= 3:
        rows.append(_MP19_KEYS[2]['label'])
    for i, row in enumerate(rows):
        y = 62 + i * 18
        pyxel.rect(32, y - 4, 232, 13, C1)
        pyxel.text(40, y, row, _MP19_COLORS[i % len(_MP19_COLORS)])
    _mp24_draw_crown(42, 127, C10)
    pyxel.text(62, 127, 'EMPIRE CROWN = UPGRADED PLAYER', C10)
    pyxel.text(38, 140, 'ROUND 1 WINNER GETS EMPIRE.', C7)
    pyxel.text(38, 152, 'THAT PLAYER ONLY NEEDS 1 MORE WIN TO WIN.', C14)
    pyxel.text(38, 164, 'IF ANOTHER PLAYER WINS ROUND 2, FINAL ROUND.', C7)
    pyxel.text(82, 180, 'PRESS ENTER TO START', C10 if (pyxel.frame_count // 12) % 2 == 0 else C7)

globals()['_draw_mp_tutorial_v19'] = _draw_mp_tutorial_v19

def _draw_mp_player_v19(p, cam_x, country_key):
    sx, sy = int(p['x'] - cam_x), int(p['y'])
    idx = p['idx']; col = _MP19_COLORS[idx % len(_MP19_COLORS)]
    if p.get('invuln', 0) > 0 and (pyxel.frame_count // 4) % 2 == 0:
        pyxel.circb(sx + 8, sy + 11, 13, C7)
    if not p.get('alive'):
        pyxel.text(sx, sy + 8, 'KO', C8); return
    body = Player.SKINS[0]['body']; cape = Player.SKINS[0]['cape']; visor = Player.SKINS[0]['visor']
    step = 1 if (p.get('on_ground') and abs(p.get('vx', 0)) > .1 and (p.get('walk', 0) // 5) % 2 == 0) else -1
    pyxel.rect(sx + 2 - p.get('facing', 1), sy + 9, 5, 11, cape)
    pyxel.rect(sx + 5, sy + 6, 7, 14, body)
    pyxel.rect(sx + 4, sy + 1, 9, 7, body)
    pyxel.rect(sx + (9 if p.get('facing', 1) > 0 else 4), sy + 3, 3, 2, visor)
    pyxel.rect(sx + 4, sy + 20, 4, 4 + step, C4)
    pyxel.rect(sx + 10, sy + 20, 4, 4 - step, C4)
    pyxel.text(sx + 1, sy - 9, 'P' + str(idx + 1), col)
    if p.get('empire'):
        _mp24_draw_crown(sx + 1, sy - 20, C10)

globals()['_draw_mp_player_v19'] = _draw_mp_player_v19

def _mp24_draw_hazard(h, cam_x):
    sx, sy = int(h['x'] - cam_x), int(h['y'])
    k = h.get('kind')
    if k == 'missile':
        pyxel.rect(sx - 3, sy - 9, 6, 14, C8); pyxel.pset(sx, sy - 10, C7)
        pyxel.line(sx - 2, sy + 5, sx, sy + 10, C10); pyxel.line(sx + 2, sy + 5, sx, sy + 10, C10)
    elif k == 'bomb':
        pyxel.circ(sx, sy, 5, C4); pyxel.circ(sx, sy, 3, C8); pyxel.pset(sx, sy - 5, C10)
    elif k == 'drone':
        pyxel.rect(sx - 10, sy - 5, 20, 10, C5); pyxel.rectb(sx - 10, sy - 5, 20, 10, C12)
        pyxel.circ(sx - 13, sy, 3, C7); pyxel.circ(sx + 13, sy, 3, C7)
    elif k == 'turret':
        pyxel.rect(sx - 8, sy, 16, 12, C4); pyxel.rectb(sx - 8, sy, 16, 12, C8); pyxel.line(sx, sy, sx + 12, sy - 7, C7)
    else:
        pyxel.circ(sx, sy, 5, C5); pyxel.circb(sx, sy, 5, C8); pyxel.text(sx - 2, sy - 12, '!', C8)

def _draw_mp_arena_v19(self):
    self.draw_background()
    pyxel.camera(int(getattr(self, 'shake_x', 0)), int(getattr(self, 'shake_y', 0)))
    self.terrain.draw(self.cam_x, _mp20_difficulty(self))
    for h in getattr(self, 'mp_arena_hazards', []):
        _mp24_draw_hazard(h, self.cam_x)
    for b in getattr(self, 'mp_arena_bullets', []):
        sx, sy = int(b['x'] - self.cam_x), int(b['y'])
        pyxel.line(sx - int(sign(b['vx']) * 7), sy, sx, sy, C7); pyxel.circ(sx, sy, 2, C10)
    country_key = self.current_country()['key']
    for p in getattr(self, 'mp_arena_players', []):
        _draw_mp_player_v19(p, self.cam_x, country_key)
    for e in self.effects: e.draw(self.cam_x)
    for pa in self.particles: pa.draw(self.cam_x)
    pyxel.camera(0, 0)
    pyxel.rect(0, 0, SCREEN_W, 27, C0); pyxel.rectb(0, 0, SCREEN_W, 27, C5)
    pyxel.text(6, 5, 'MP ROUND ' + str(getattr(self, 'mp_round', 1)), C14)
    for p in getattr(self, 'mp_arena_players', []):
        x = 76 + p['idx'] * 80
        pyxel.text(x, 5, 'P' + str(p['idx'] + 1) + ' ' + _v19_survival_km_text(int(p.get('dist', 0))), C10 if p.get('empire') else C7)
        for hp_i in range(3):
            px = x + hp_i * 8
            pyxel.rectb(px, 15, 6, 6, C8)
            if hp_i < max(0, int(p.get('hp', 0))):
                pyxel.rect(px + 1, 16, 4, 4, C8)
        if p.get('empire'):
            _mp24_draw_crown(x + 34, 14, C10)

globals()['_draw_mp_arena_v19'] = _draw_mp_arena_v19

def _draw_mp_round_result_v19(self):
    pyxel.cls(C0)
    pyxel.rect(28, 28, 264, 144, C0); pyxel.rectb(28, 28, 264, 144, C10)
    pyxel.text(82, 44, 'ROUND ' + str(getattr(self, 'mp_round', 1)) + ' RESULT', C14)
    pyxel.text(50, 64, str(getattr(self, 'mp_round_message', 'ROUND COMPLETE'))[:48], C7)
    for i, score in enumerate(getattr(self, 'mp_scores', [])):
        y = 88 + i * 14
        if i in getattr(self, 'mp_empire_players', set()):
            _mp24_draw_crown(58, y - 2, C10)
        pyxel.text(76, y, 'P' + str(i + 1) + '  ' + _v19_survival_km_text(int(score)), C10 if i in getattr(self, 'mp_empire_players', set()) else C7)
    pyxel.text(56, 138, 'CROWN PLAYER NEEDS ONLY 1 MORE WIN.', C14)
    pyxel.text(76, 154, 'PRESS ENTER FOR NEXT ROUND', C10 if (pyxel.frame_count // 10) % 2 == 0 else C7)

globals()['_draw_mp_round_result_v19'] = _draw_mp_round_result_v19

# ---------------- V24: menu balance top-right ----------------
_old_draw_menu_v24 = Game.draw_menu
def _draw_menu_v24(self):
    _old_draw_menu_v24(self)
    pyxel.camera(0, 0)
    txt = 'COINS ' + str(getattr(self, 'coins_total', 0)) + '$'
    x = SCREEN_W - len(txt) * 4 - 8
    pyxel.rect(x - 4, 4, len(txt) * 4 + 6, 11, C0)
    pyxel.rectb(x - 4, 4, len(txt) * 4 + 6, 11, C10)
    pyxel.text(x, 7, txt, C10)
Game.draw_menu = _draw_menu_v24


# ---------------- V25: pace, quake timing, boss cinema, competitive multiplayer polish ----------------
FINAL_BUILD_TAG = 'v25-pace-quake-competitive-polish'

def _v25_mp_modes(game):
    try:
        game.mp_mode_names = ['SURVIVAL ROTATION', 'COMPETITIVE']
        game.mp_mode_index = int(clamp(getattr(game, 'mp_mode_index', 0), 0, 1))
    except Exception:
        pass

def _v25_mp_mode_label(game):
    try:
        return 'COMPETITIVE' if int(getattr(game, 'mp_mode_index', 0)) == 1 else 'SURVIVAL MODE'
    except Exception:
        return 'SURVIVAL MODE'

_old_game_init_v25 = Game.__init__
def _game_init_v25(self):
    _old_game_init_v25(self)
    _v25_mp_modes(self)
Game.__init__ = _game_init_v25

_old_danger_scale_v25 = Game.danger_scale
def _danger_scale_v25(self):
    try:
        if getattr(self, 'state', '') == 'mp_arena':
            return _old_danger_scale_v25(self)
        return max(0.8, _old_danger_scale_v25(self) * 0.93)
    except Exception:
        return _old_danger_scale_v25(self)
Game.danger_scale = _danger_scale_v25

_old_bullet_init_v25 = Bullet.__init__
def _bullet_init_v25(self, x, y, vx, vy, dmg=1, color=C7, radius=2, friendly=True, life=90):
    if not friendly:
        vx *= 0.91
        vy *= 0.91
        life += 8
    _old_bullet_init_v25(self, x, y, vx, vy, dmg, color, radius, friendly, life)
Bullet.__init__ = _bullet_init_v25

_old_missile_init_v25 = MissileEnemy.__init__
def _missile_init_v25(self, x, speed):
    _old_missile_init_v25(self, x, speed * 0.92)
MissileEnemy.__init__ = _missile_init_v25

_old_drone_init_v25 = DroneEnemy.__init__
def _drone_init_v25(self, x, y, tx, ty, speed):
    _old_drone_init_v25(self, x, y, tx, ty, speed * 0.93)
DroneEnemy.__init__ = _drone_init_v25

_old_heavy_init_v25 = HeavyBombEnemy.__init__
def _heavy_init_v25(self, x, y, speed):
    _old_heavy_init_v25(self, x, y, speed * 0.92)
HeavyBombEnemy.__init__ = _heavy_init_v25

_old_bomber_init_v25 = BomberEnemy.__init__
def _bomber_init_v25(self, x, y, direction, drop_x):
    _old_bomber_init_v25(self, x, y, direction, drop_x)
    try:
        self.vx *= 0.92
    except Exception:
        pass
BomberEnemy.__init__ = _bomber_init_v25

_old_player_update_v25_shootfix = Player.update
def _player_update_v25_shootfix(self, difficulty, dev_mode=False):
    result = _old_player_update_v25_shootfix(self, difficulty, dev_mode)
    if getattr(self, 'shot_cd', 0) > 34:
        self.shot_cd = 34
    if getattr(self, 'special_cd', 0) > 40:
        self.special_cd = 40
    if getattr(self, 'weapon_index', 0) >= len(getattr(self, 'WEAPONS', ['BLASTER'])):
        self.weapon_index = 0
    return result
Player.update = _player_update_v25_shootfix

# Quakes: a little earlier, denser cracks, less text bulk, no start-of-level quake.
def _v25_flash_text(x, y, msg, col=None):
    col = col if col is not None else (C8 if (pyxel.frame_count // 7) % 2 == 0 else C10)
    pyxel.text(int(x), int(y), str(msg), col)

def _v25_center_text(msg, y, col=None):
    x = max(4, (SCREEN_W - len(str(msg)) * 4) // 2)
    _v25_flash_text(x, y, msg, col)

globals()['_v24_flash_text'] = _v25_flash_text
globals()['_v24_center_text'] = _v25_center_text

def _v25_prune_terrain(game, softer=False):
    try:
        cx = getattr(game, 'cam_x', 0) + SCREEN_W // 2
        craters = list(getattr(game.terrain, 'craters', []))
        uplifts = list(getattr(game.terrain, 'uplifts', []))
        crater_cap = 64 if not softer else 52
        uplift_cap = 68 if not softer else 56
        if len(craters) > crater_cap:
            craters.sort(key=lambda c: abs(c.get('x', 0) - cx))
            game.terrain.craters = sorted(craters[:crater_cap], key=lambda c: c.get('x', 0))
        if len(uplifts) > uplift_cap:
            uplifts.sort(key=lambda u: abs(u.get('x', 0) - cx))
            game.terrain.uplifts = sorted(uplifts[:uplift_cap], key=lambda u: u.get('x', 0))
        if getattr(game, 'v19_quake_active_timer', 0) <= 0:
            game.texts = [t for t in getattr(game, 'texts', []) if getattr(t, 'text', '') != '!'][-60:]
    except Exception:
        pass

def _v25_add_quake_trench(terrain, x, rng):
    terrain.craters.append({'x': x, 'r': rng.randint(18, 29), 'gap_r': rng.randint(7, 11), 'depth': rng.randint(18, 33)})
    if len(terrain.craters) > 68:
        terrain.craters.pop(0)

def _v25_add_quake_uplift(terrain, x, rng):
    if not hasattr(terrain, 'uplifts'):
        terrain.uplifts = []
    terrain.uplifts.append({'x': x, 'r': rng.randint(40, 82), 'height': rng.randint(10, 22)})
    if len(terrain.uplifts) > 72:
        terrain.uplifts.pop(0)

globals()['_v19_add_quake_trench'] = _v25_add_quake_trench
globals()['_v19_add_quake_uplift'] = _v25_add_quake_uplift
globals()['_v22_add_quake_trench'] = _v25_add_quake_trench
globals()['_v22_add_quake_uplift'] = _v25_add_quake_uplift

def _v25_shape_quake_span(game, start, end, seed):
    rng = random.Random(seed)
    x = start + rng.randint(35, 95)
    last_trench = -999999
    while x < end:
        x += rng.randint(70, 124)
        if x >= end:
            break
        if rng.random() < 0.84 and abs(x - last_trench) > 92:
            _v25_add_quake_trench(game.terrain, x, rng)
            try:
                _v24_mark_crack(game, x, rng)
            except Exception:
                pass
            last_trench = x
            if rng.random() < 0.82:
                _v25_add_quake_uplift(game.terrain, x + rng.choice([-1, 1]) * rng.randint(34, 74), rng)
        else:
            _v25_add_quake_uplift(game.terrain, x + rng.randint(-44, 58), rng)
        if rng.random() < 0.72:
            _v25_add_quake_uplift(game.terrain, x + rng.randint(-68, 82), rng)
    _v25_prune_terrain(game, softer=False)

def _v19_shape_whole_quake_map(game):
    if '_v20_quake_state' in globals():
        _v20_quake_state(game)
    if _v20_boss_active(game) or not getattr(game, 'v19_quake_level', False) or getattr(game, 'v19_quake_shaped', False):
        return
    try:
        length = _v19_level_len(game)
    except Exception:
        length = LEVEL_DISTANCE
    start = int(getattr(game, 'checkpoint_x', 40)) + max(220, int(length * 0.18))
    end = int(getattr(game, 'checkpoint_x', 40)) + length - 90
    _v25_shape_quake_span(game, start, end, int(getattr(game, 'v19_quake_seed', 0)) + int(getattr(game, 'level', 1)) * 3391)
    game.v19_quake_shaped = True
    game.v19_quake_active_timer = FPS * 8
    game.add_shake(10, 4)
globals()['_v19_shape_whole_quake_map'] = _v19_shape_whole_quake_map

def _v20_shape_survival_quake_map(game):
    if '_v20_quake_state' in globals():
        _v20_quake_state(game)
    if _v20_boss_active(game) or getattr(game, 'v19_quake_shaped', False):
        return
    game.v20_survival_quake_serial = getattr(game, 'v20_survival_quake_serial', 0) + 1
    start = int(game.cam_x + SCREEN_W + 96)
    end = start + 2400
    _v25_shape_quake_span(game, start, end, int(getattr(game, 'v19_quake_seed', 0)) + game.v20_survival_quake_serial * 3221)
    game.v19_quake_shaped = True
    game.v19_quake_active_timer = FPS * 8
    game.add_shake(10, 4)
globals()['_v20_shape_survival_quake_map'] = _v20_shape_survival_quake_map

_old_update_earthquake_v25_base = globals().get('_update_earthquake_v14')
def _update_earthquake_v25(game):
    if _old_update_earthquake_v25_base:
        _old_update_earthquake_v25_base(game)
    try:
        if getattr(game, 'state', '') == 'playing' and not _v20_is_multiplayer_context(game) and not _v20_boss_active(game):
            if (not game.is_survival()) and getattr(game, 'v19_quake_level', False) and not getattr(game, 'v24_quake_mid_warning_started', False):
                ratio = game.distance / max(1, _v19_level_len(game))
                if ratio >= 0.32:
                    game.v24_quake_mid_warning_started = True
                    game.v19_quake_warning_timer = FPS * 2
                    game.v19_quake_impact_pending = True
            if game.is_survival() and getattr(game, 'v20_survival_quake_next_x', 999999) > 1500:
                game.v20_survival_quake_next_x = max(1100, game.v20_survival_quake_next_x - 260)
        if getattr(game, 'v19_quake_active_timer', 0) <= 0 and getattr(game, 'nuke_aftershock_timer', 0) <= 0 and pyxel.frame_count % 45 == 0:
            _v25_prune_terrain(game, softer=True)
    except Exception:
        pass
globals()['_update_earthquake_v14'] = _update_earthquake_v25

def _draw_earthquake_overlay_v25(game):
    if _v20_is_multiplayer_context(game) or _v20_boss_active(game):
        return
    pyxel.camera(0, 0)
    flash = C8 if (pyxel.frame_count // 7) % 2 == 0 else C10
    if getattr(game, 'v19_quake_level', False) and getattr(game, 'v19_quake_warning_timer', 0) > 0:
        sec = max(1, (game.v19_quake_warning_timer + FPS - 1) // FPS)
        _v25_center_text('SEISMIC WAVES IN ' + str(sec) + '!', 74, flash)
        _v25_center_text('WATCH ! CRACKS', 87, flash)
    elif getattr(game, 'v19_quake_level', False) and getattr(game, 'v19_quake_active_timer', 0) > 0:
        _v25_center_text('EARTHQUAKE!', 9, flash)
        if (pyxel.frame_count // 20) % 2 == 0:
            _v25_center_text('! = TRENCH BREAK', 21, flash)
    if getattr(game, 'nuke_aftershock_timer', 0) > 0:
        _v25_center_text('AFTERSHOCK!', 82, flash)
        _v25_center_text('!', 95, flash)
globals()['_draw_earthquake_overlay_v14'] = _draw_earthquake_overlay_v25
globals()['_draw_earthquake_overlay_v22'] = _draw_earthquake_overlay_v25
globals()['_draw_earthquake_overlay_v21'] = _draw_earthquake_overlay_v25
globals()['_draw_earthquake_overlay_v24'] = _draw_earthquake_overlay_v25

# Boss gets occasional comic laser sweeps; existing dash grace dodges them.
_old_boss_init_v25 = BossEnemy.__init__
def _boss_init_v25(self, x):
    _old_boss_init_v25(self, x)
    self.v25_cinema_cd = random.randint(FPS * 4, FPS * 7)
    self.v25_cinema_timer = 0
    self.v25_cinema_y = 96
    self.v25_cinema_hit = False
BossEnemy.__init__ = _boss_init_v25

_old_boss_update_v25 = BossEnemy.update
def _boss_update_v25(self, game):
    if getattr(self, 'v25_cinema_timer', 0) > 0:
        self.v25_cinema_timer -= 1
        if self.v25_cinema_timer > 32:
            self.v25_cinema_y = int(game.player.y + PLAYER_H / 2)
            if self.v25_cinema_timer % 10 == 0:
                game.particles.append(Particle(self.x + 38, self.y + 30, random.uniform(-1.4,1.4), random.uniform(-1.4,1.4), random.choice([C8,C10,C14,C7]), 18, size=2, gravity=0))
        else:
            py, ph = game.player.y, PLAYER_H
            if (not getattr(self, 'v25_cinema_hit', False)) and py < self.v25_cinema_y + 10 and py + ph > self.v25_cinema_y - 10:
                if getattr(game.player, 'boss_laser_dash_grace', 0) > 0 or getattr(game.player, 'dash_timer', 0) > 0:
                    self.v25_cinema_hit = True
                    game.player.boss_laser_dash_grace = 0
                    game.player.invuln = max(getattr(game.player, 'invuln', 0), 26)
                    game.add_text(game.player.x - 2, game.player.y - 15, 'CINEMA LASER DODGED!', C14)
                    game.add_shake(8, 2)
                else:
                    self.v25_cinema_hit = True
                    game.player_hit(1)
                    game.add_shake(12, 4)
        return
    _old_boss_update_v25(self, game)
    if not getattr(self, 'alive', True):
        return
    if getattr(self, 'laser_charge', 0) > 0 or getattr(self, 'laser_timer', 0) > 0 or getattr(self, 'super_laser_timer', 0) > 0:
        return
    self.v25_cinema_cd = getattr(self, 'v25_cinema_cd', FPS * 5) - 1
    if self.v25_cinema_cd <= 0 and self.hp < self.max_hp * 0.94:
        self.v25_cinema_timer = FPS + 22
        self.v25_cinema_y = int(game.player.y + PLAYER_H / 2)
        self.v25_cinema_hit = False
        self.v25_cinema_cd = random.randint(FPS * 5, FPS * 8)
        game.add_text(self.x - 16, self.y - 18, 'COMIC LASER PANEL', C10)
        game.add_shake(12, 2)
BossEnemy.update = _boss_update_v25

_old_boss_draw_v25 = BossEnemy.draw
def _boss_draw_v25(self, cam_x):
    _old_boss_draw_v25(self, cam_x)
    timer = getattr(self, 'v25_cinema_timer', 0)
    if timer <= 0:
        return
    sx = int(self.x - cam_x)
    sy = int(self.y)
    yy = int(getattr(self, 'v25_cinema_y', 96))
    pyxel.camera(0, 0)
    if timer > 32:
        pyxel.rect(18, 26, 124, 34, C0)
        pyxel.rectb(18, 26, 124, 34, C10)
        pyxel.text(29, 36, 'BOSS CHARGES', C10)
        pyxel.text(32, 48, 'DASH BEFORE FLASH', C7)
        col = C8 if (pyxel.frame_count // 4) % 2 == 0 else C10
        pyxel.line(0, yy, SCREEN_W, yy, col)
        pyxel.line(0, yy - 4, SCREEN_W, yy - 4, C5)
        pyxel.line(0, yy + 4, SCREEN_W, yy + 4, C5)
    else:
        pyxel.rect(0, yy - 5, SCREEN_W, 11, C8)
        pyxel.line(0, yy, SCREEN_W, yy, C10)
        pyxel.line(0, yy - 6, SCREEN_W, yy - 6, C7)
        pyxel.line(0, yy + 6, SCREEN_W, yy + 6, C7)
        pyxel.text(max(4, sx - 4), max(25, sy - 18), 'KRAK!', C14)
BossEnemy.draw = _boss_draw_v25

# Menu: Multiplayer now explicitly selects Survival Mode or Competitive, and coin balance only remains top right.
_old_update_menu_v25 = Game.update_menu
def _update_menu_v25(self):
    _v25_mp_modes(self)
    if pyxel.btnp(pyxel.KEY_LEFT):
        self.mode_index = (self.mode_index - 1) % len(self.mode_names)
    if pyxel.btnp(pyxel.KEY_RIGHT):
        self.mode_index = (self.mode_index + 1) % len(self.mode_names)
    if pyxel.btnp(pyxel.KEY_UP):
        self.skin_index = (self.skin_index - 1) % len(Player.SKINS)
    if pyxel.btnp(pyxel.KEY_DOWN):
        self.skin_index = (self.skin_index + 1) % len(Player.SKINS)
    if pyxel.btnp(pyxel.KEY_U) and self.skin_index not in self.unlocked_skins:
        cost = SKIN_PRICES[self.skin_index]
        if self.coins_total >= cost:
            self.coins_total -= cost
            self.unlocked_skins.append(self.skin_index)
            self.save_data['coins_total'] = self.coins_total
            self.save_data['unlocked_skins'] = self.unlocked_skins
            save_save(self.save_data)
    if self.is_multiplayer():
        if pyxel.btnp(pyxel.KEY_A):
            self.mp_player_count = max(2, int(getattr(self, 'mp_player_count', 2)) - 1)
        if pyxel.btnp(pyxel.KEY_D):
            self.mp_player_count = min(3, int(getattr(self, 'mp_player_count', 2)) + 1)
        if pyxel.btnp(pyxel.KEY_W) or pyxel.btnp(pyxel.KEY_S):
            self.mp_mode_index = (int(getattr(self, 'mp_mode_index', 0)) + 1) % 2
        if pyxel.btnp(pyxel.KEY_RETURN) and self.skin_index in self.unlocked_skins:
            self.player.skin_index = self.skin_index
            self.player.country_index = self.country_index
            if int(getattr(self, 'mp_mode_index', 0)) == 1:
                _mp19_setup_select(self)
            else:
                self.reset_multiplayer_session()
            return
        return
    if pyxel.btnp(pyxel.KEY_RETURN):
        if self.skin_index in self.unlocked_skins:
            self.player.skin_index = self.skin_index
            self.player.country_index = self.country_index
            self.reset_run()
Game.update_menu = _update_menu_v25

_old_draw_menu_v25 = Game.draw_menu
def _draw_menu_v25(self):
    _v25_mp_modes(self)
    _old_draw_menu_v25(self)
    pyxel.camera(0, 0)
    pyxel.rect(214, 114, 78, 14, C0)
    if self.skin_index not in self.unlocked_skins:
        cost = SKIN_PRICES[self.skin_index]
        col = C10 if getattr(self, 'coins_total', 0) >= cost else C8
        pyxel.text(215, 132, 'U BUY: ' + str(cost) + '$', col)
    pyxel.rect(176, 162, 102, 12, C0)
    if self.is_multiplayer():
        pyxel.rect(154, 118, 96, 11, C0)
        pyxel.text(160, 122, _v25_mp_mode_label(self), C10)
        pyxel.rect(124, 132, 160, 11, C0)
        pyxel.text(126, 136, 'A/D PLAYERS, W/S TYPE', C6)
    txt = 'COINS ' + str(getattr(self, 'coins_total', 0)) + '$'
    x = SCREEN_W - len(txt) * 4 - 8
    pyxel.rect(x - 4, 4, len(txt) * 4 + 6, 11, C0)
    pyxel.rectb(x - 4, 4, len(txt) * 4 + 6, 11, C10)
    pyxel.text(x, 7, txt, C10)
Game.draw_menu = _draw_menu_v25

# Competitive multiplayer: slower, spread gun, double jump, powerups that spawn coloured attacks.
_MP19_COLORS = [C8, C12, C10]
globals()['_MP19_COLORS'] = _MP19_COLORS
_MP25_COLOR_NAMES = ['RED', 'BLUE', 'YELLOW']

def _mp25_col(owner):
    if owner is None:
        return C7
    return _MP19_COLORS[int(owner) % len(_MP19_COLORS)]

def _mp19_setup_select(self):
    _v25_mp_modes(self)
    self.mp_player_count = int(clamp(getattr(self, 'mp_player_count', 2), 2, 3))
    self.state = 'mp_select_count'
    self.mp_round = 1
    self.mp_empire_players = set()
    self.mp_match_winner = None
    self.mp_round_winner = None
    self.mp_round_message = ''
    self.mp_scores = [0 for _ in range(3)]
    stop_story_music()
globals()['_mp19_setup_select'] = _mp19_setup_select

def _mp19_start_round(game):
    count = int(clamp(getattr(game, 'mp_player_count', 2), 2, 3))
    game.terrain.reset()
    game.mp_arena_players = []
    game.mp_arena_bullets = []
    game.mp_arena_hazards = []
    game.mp_powerups = []
    game.mp_powerup_cd = FPS * 4
    game.mp_arena_spawn_cd = 68
    game.mp_arena_timer = 0
    game.cam_x = 0
    game.shake_timer = 0
    game.effects.clear(); game.particles.clear(); game.texts.clear()
    game.field_order = None
    game.v19_quake_level = False
    game.nuke_aftershock_timer = 0
    for i in range(count):
        x = 58 + i * 34
        y = game.terrain.ground_y_at(x + 8, _mp20_difficulty(game)) - PLAYER_H
        empire = i in getattr(game, 'mp_empire_players', set())
        game.mp_arena_players.append({
            'idx': i, 'x': x, 'y': y, 'vx': 0.0, 'vy': 0.0, 'facing': 1,
            'hp': 3, 'alive': True, 'dist': 0, 'survive_frames': 0, 'invuln': 90, 'shot_cd': 0,
            'empire': empire, 'on_ground': True, 'dead_timer': 0, 'walk': 0, 'jump_count': 0,
        })
    game.mp_scores = [0 for _ in range(count)]
    game.state = 'mp_arena'
    stop_story_music()
globals()['_mp19_start_round'] = _mp19_start_round

def _mp19_player_hit(game, p, dmg=1):
    if not p.get('alive') or p.get('invuln', 0) > 0:
        return
    p['hp'] -= dmg
    p['invuln'] = 42
    game.spawn_explosion(p['x'] + 8, p['y'] + 10, 10 + dmg * 2, 'normal')
    game.add_shake(5, 3)
    if p['hp'] <= 0:
        p['alive'] = False
        p['dead_timer'] = 45
        game.spawn_explosion(p['x'] + 8, p['y'] + 10, 22, 'normal')
globals()['_mp19_player_hit'] = _mp19_player_hit

def _mp19_update_player(game, p):
    keys = _MP19_KEYS[p['idx']]
    if not p.get('alive'):
        p['dead_timer'] = max(0, p.get('dead_timer', 0) - 1)
        return
    p['survive_frames'] = p.get('survive_frames', 0) + 1
    p['invuln'] = max(0, p.get('invuln', 0) - 1)
    p['shot_cd'] = max(0, min(32, p.get('shot_cd', 0)) - 1)
    speed = 1.56 + (0.08 if p.get('empire') else 0.0)
    jump = -5.25
    vx = 0
    if pyxel.btn(keys['left']):
        vx -= speed; p['facing'] = -1
    if pyxel.btn(keys['right']):
        vx += speed; p['facing'] = 1
    p['vx'] = vx
    if pyxel.btnp(keys['jump']) and int(p.get('jump_count', 0)) < 2:
        p['vy'] = jump + (0.16 if p.get('jump_count', 0) else 0)
        p['on_ground'] = False
        p['jump_count'] = int(p.get('jump_count', 0)) + 1
    if pyxel.btn(keys['down']) and not p.get('on_ground'):
        p['vy'] += 0.32
    p['vy'] = min(p['vy'] + GRAVITY, MAX_FALL * 0.96)
    p['x'] += p['vx']; p['y'] += p['vy']
    p['x'] = max(0, p['x'])
    foots = (p['x'] + 3, p['x'] + 8, p['x'] + 13)
    grounds = [game.terrain.ground_y_at(f, _mp20_difficulty(game)) for f in foots]
    target_ground = min(grounds)
    if p['y'] >= target_ground - PLAYER_H:
        p['y'] = target_ground - PLAYER_H; p['vy'] = 0; p['on_ground'] = True; p['jump_count'] = 0
    else:
        p['on_ground'] = False
    if p['y'] > SCREEN_H + 44:
        _mp19_player_hit(game, p, 3)
    p['walk'] = p.get('walk', 0) + 1 if abs(p['vx']) > 0.1 and p.get('on_ground') else 0
    p['dist'] = int(p.get('survive_frames', 0) * .70)
    if pyxel.btnp(keys['shoot']) and p['shot_cd'] <= 0:
        p['shot_cd'] = 25 if not p.get('empire') else 22
        for vy in (-0.62, 0, 0.62):
            game.mp_arena_bullets.append({
                'x': p['x'] + 8 + p['facing'] * 7, 'y': p['y'] + 11,
                'vx': p['facing'] * 5.0, 'vy': vy, 'owner': p['idx'], 'life': 58,
                'dmg': 2, 'style': 'spread', 'seed': random.randint(0, 9999)
            })
        sfx(0, 0)
globals()['_mp19_update_player'] = _mp19_update_player

def _mp25_spawn_powerup(game):
    x = random.randint(int(game.cam_x + 36), int(game.cam_x + SCREEN_W - 36))
    game.mp_powerups.append({'x': x, 'y': -14, 'vy': 1.15, 'kind': random.choice(['drone', 'gas', 'strike', 'mine']), 'alive': True, 'spin': random.randint(0, 99)})

def _mp25_nearest_enemy_player(game, owner, x):
    candidates = [p for p in getattr(game, 'mp_arena_players', []) if p.get('alive') and p.get('idx') != owner]
    if not candidates:
        return None
    return min(candidates, key=lambda p: abs(p.get('x', 0) - x))

def _mp25_activate_powerup(game, owner, kind):
    col = _mp25_col(owner)
    target = _mp25_nearest_enemy_player(game, owner, game.cam_x + SCREEN_W / 2)
    tx = target['x'] if target else game.cam_x + random.randint(60, SCREEN_W - 60)
    ty = target['y'] if target else 90
    if kind == 'drone':
        for off in (-18, 18):
            game.mp_arena_hazards.append({'kind': 'hunter', 'owner': owner, 'x': game.cam_x + (20 if off < 0 else SCREEN_W - 20), 'y': 72 + off / 3, 'vx': 0, 'vy': 0, 'dmg': 2, 'alive': True, 'life': FPS * 8})
    elif kind == 'gas':
        game.mp_arena_hazards.append({'kind': 'gas', 'owner': owner, 'x': tx, 'y': ty + 8, 'r': 7, 'dmg': 2, 'alive': True, 'life': FPS * 4, 'hit': set()})
    elif kind == 'strike':
        for i in range(3):
            game.mp_arena_hazards.append({'kind': 'owner_missile', 'owner': owner, 'x': tx + random.randint(-45, 45), 'y': -24 - i * 18, 'vy': 2.15, 'dmg': 2, 'alive': True})
    else:
        gy = game.terrain.ground_y_at(tx, _mp20_difficulty(game))
        game.mp_arena_hazards.append({'kind': 'owner_mine', 'owner': owner, 'x': tx, 'y': gy - 5, 'timer': FPS * 4, 'dmg': 2, 'alive': True})
    game.add_text(tx, max(34, ty - 24), _MP25_COLOR_NAMES[owner] + ' POWER!', col)

def _mp25_hazard_box(h):
    k = h.get('kind')
    if k == 'hunter': return h['x'] - 12, h['y'] - 7, 24, 14
    if k == 'gas': return h['x'] - h.get('r', 14), h['y'] - h.get('r', 14), h.get('r', 14)*2, h.get('r', 14)*2
    if k == 'turret': return h['x'] - 9, h['y'] - 4, 18, 16
    if k in ('bomb', 'owner_missile', 'missile'): return h['x'] - 5, h['y'] - 5, 10, 10
    return h['x'] - 6, h['y'] - 6, 12, 12

def _mp24_spawn_hazard(game):
    left = int(game.cam_x + 18)
    right = int(game.cam_x + SCREEN_W - 22)
    kind = random.choices(['missile', 'bomb', 'drone', 'turret', 'mine'], [30, 24, 20, 12, 14])[0]
    live = [p for p in getattr(game, 'mp_arena_players', []) if p.get('alive')]
    x = random.randint(left, right) if not live or random.random() > .45 else int(clamp(random.choice(live)['x'] + random.randint(-60, 75), left, right))
    if kind == 'missile':
        game.mp_arena_hazards.append({'kind': 'missile', 'owner': None, 'x': x, 'y': -16, 'vy': random.uniform(1.65, 2.75), 'dmg': 1, 'alive': True})
    elif kind == 'bomb':
        game.mp_arena_hazards.append({'kind': 'bomb', 'owner': None, 'x': x, 'y': -12, 'vx': random.uniform(-0.28, 0.28), 'vy': random.uniform(1.45, 2.2), 'dmg': 1, 'alive': True})
    elif kind == 'drone':
        side = random.choice([-1, 1]); y = random.randint(58, 116)
        game.mp_arena_hazards.append({'kind': 'drone', 'owner': None, 'x': game.cam_x + (-24 if side > 0 else SCREEN_W + 24), 'y': y, 'vx': side * random.uniform(0.95, 1.45), 'dmg': 1, 'alive': True})
    elif kind == 'turret':
        gy = game.terrain.ground_y_at(x, _mp20_difficulty(game))
        game.mp_arena_hazards.append({'kind': 'turret', 'owner': None, 'x': x, 'y': gy - 12, 'timer': random.randint(170, 245), 'dmg': 1, 'alive': True})
    else:
        gy = game.terrain.ground_y_at(x, _mp20_difficulty(game))
        game.mp_arena_hazards.append({'kind': 'mine', 'owner': None, 'x': x, 'y': gy - 5, 'timer': random.randint(120, 200), 'dmg': 1, 'alive': True})
globals()['_mp24_spawn_hazard'] = _mp24_spawn_hazard

def _mp19_update_bullets_and_hazards(game):
    if not hasattr(game, 'mp_powerups'):
        game.mp_powerups = []
        game.mp_powerup_cd = FPS * 4
    for b in game.mp_arena_bullets:
        b['x'] += b['vx']; b['y'] += b.get('vy', 0); b['life'] -= 1
    for b in game.mp_arena_bullets:
        if b.get('life', 0) <= 0:
            continue
        for p in game.mp_arena_players:
            if p.get('alive') and p.get('idx') != b.get('owner') and rects_overlap(b['x']-3, b['y']-3, 6, 6, p['x']+3, p['y']+3, 10, 18):
                b['life'] = 0
                _mp19_player_hit(game, p, int(b.get('dmg', 2)))
                break
    game.mp_arena_spawn_cd -= 1
    if game.mp_arena_spawn_cd <= 0:
        game.mp_arena_spawn_cd = max(36, 78 - game.mp_arena_timer // 520)
        _mp24_spawn_hazard(game)
        if random.random() < 0.12:
            _mp24_spawn_hazard(game)
    game.mp_powerup_cd -= 1
    if game.mp_powerup_cd <= 0:
        game.mp_powerup_cd = random.randint(FPS * 5, FPS * 8)
        _mp25_spawn_powerup(game)
    for pu in game.mp_powerups:
        if not pu.get('alive'):
            continue
        pu['spin'] = pu.get('spin', 0) + 1
        pu['vy'] += 0.025
        pu['y'] += pu['vy']
        gy = game.terrain.ground_y_at(pu['x'], _mp20_difficulty(game)) - 9
        if pu['y'] > gy:
            pu['y'] = gy
            pu['vy'] = 0
        for p in game.mp_arena_players:
            if p.get('alive') and rects_overlap(p['x']+2, p['y']+2, 12, 20, pu['x']-7, pu['y']-7, 14, 14):
                pu['alive'] = False
                _mp25_activate_powerup(game, p['idx'], pu.get('kind', 'drone'))
                sfx(0, 3)
                break
    for h in game.mp_arena_hazards:
        if not h.get('alive'):
            continue
        k = h.get('kind')
        if k in ('missile', 'owner_missile'):
            h['y'] += h.get('vy', 2.0)
            gy = game.terrain.ground_y_at(h['x'], _mp20_difficulty(game))
            if h['y'] >= gy:
                h['alive'] = False; game.spawn_explosion(h['x'], gy, 13, 'normal'); game.add_shake(4, 3)
        elif k == 'bomb':
            h['vy'] += 0.09; h['x'] += h.get('vx', 0); h['y'] += h['vy']
            gy = game.terrain.ground_y_at(h['x'], _mp20_difficulty(game))
            if h['y'] >= gy:
                h['alive'] = False; game.spawn_explosion(h['x'], gy, 16, 'normal'); game.add_shake(5, 3)
        elif k == 'drone':
            h['x'] += h['vx']; h['y'] += math.sin((pyxel.frame_count + int(h['x'])) * .08) * .22
        elif k == 'hunter':
            h['life'] -= 1
            target = _mp25_nearest_enemy_player(game, h.get('owner'), h.get('x', 0))
            if target:
                dx = target['x'] + 8 - h['x']; dy = target['y'] + 10 - h['y']
                d = max(1, math.sqrt(dx*dx + dy*dy))
                h['x'] += dx / d * 1.25; h['y'] += dy / d * 1.25
            if h['life'] <= 0:
                h['alive'] = False
        elif k == 'gas':
            h['life'] -= 1
            h['r'] = min(24, h.get('r', 7) + 0.18)
            if h['life'] <= 0:
                h['alive'] = False
        elif k == 'turret':
            h['timer'] -= 1; h['y'] = game.terrain.ground_y_at(h['x'], _mp20_difficulty(game)) - 12
            if h['timer'] <= 0: h['alive'] = False
        elif k in ('mine', 'owner_mine'):
            h['timer'] -= 1
            if h['timer'] <= 0:
                h['alive'] = False; game.spawn_explosion(h['x'], h['y'], 15, 'normal')
    for b in game.mp_arena_bullets:
        if b['life'] <= 0:
            continue
        for h in game.mp_arena_hazards:
            if not h.get('alive') or h.get('owner') == b.get('owner'):
                continue
            hx, hy, hw, hh = _mp25_hazard_box(h)
            if rects_overlap(b['x'] - 2, b['y'] - 2, 4, 4, hx, hy, hw, hh):
                h['alive'] = False; b['life'] = 0
                game.spawn_explosion(h['x'], h['y'], 12, 'normal')
                break
    for h in game.mp_arena_hazards:
        if not h.get('alive'):
            continue
        hx, hy, hw, hh = _mp25_hazard_box(h)
        for p in game.mp_arena_players:
            if not p.get('alive') or p.get('idx') == h.get('owner'):
                continue
            if rects_overlap(p['x'] + 3, p['y'] + 3, 10, 18, hx, hy, hw, hh):
                if h.get('kind') == 'gas':
                    hit = h.setdefault('hit', set())
                    if p['idx'] in hit:
                        continue
                    hit.add(p['idx'])
                else:
                    h['alive'] = False
                _mp19_player_hit(game, p, int(h.get('dmg', 1)))
                break
    game.mp_arena_bullets = [b for b in game.mp_arena_bullets if b['life'] > 0 and game.cam_x - 70 < b['x'] < game.cam_x + SCREEN_W + 90]
    game.mp_arena_hazards = [h for h in game.mp_arena_hazards if h.get('alive') and game.cam_x - 90 < h['x'] < game.cam_x + SCREEN_W + 90 and h.get('y', 0) < SCREEN_H + 55]
    game.mp_powerups = [p for p in game.mp_powerups if p.get('alive') and game.cam_x - 40 < p['x'] < game.cam_x + SCREEN_W + 40]
globals()['_mp19_update_bullets_and_hazards'] = _mp19_update_bullets_and_hazards

def _update_mp_arena_v19(self):
    self.mp_arena_timer = getattr(self, 'mp_arena_timer', 0) + 1
    self.field_order = None; self.v19_quake_level = False; self.nuke_aftershock_timer = 0
    for p in list(getattr(self, 'mp_arena_players', [])):
        _mp19_update_player(self, p)
    _mp24_shared_camera(self)
    _mp19_update_bullets_and_hazards(self)
    _mp24_shared_camera(self)
    for e in self.effects: e.update()
    for pa in self.particles: pa.update()
    for t in self.texts: t.update()
    self.effects = [e for e in self.effects if e.alive]
    self.particles = [p for p in self.particles if p.alive]
    self.texts = [t for t in self.texts if t.alive][-35:]
    self.update_shake()
    live_count = sum(1 for p in self.mp_arena_players if p.get('alive'))
    if live_count <= 1 and self.mp_arena_timer > FPS * 4:
        _mp19_finish_round(self)
    elif self.mp_arena_timer > FPS * 96:
        _mp19_finish_round(self)
globals()['_update_mp_arena_v19'] = _update_mp_arena_v19

def _draw_mp_select_count_v19(self):
    pyxel.cls(C0)
    pyxel.rect(18, 14, 284, 172, C0); pyxel.rectb(18, 14, 284, 172, C12)
    pyxel.text(72, 28, 'COMPETITIVE MULTIPLAYER', C14)
    pyxel.text(42, 46, 'SURVIVAL MODE IS CHOSEN FROM THE MAIN MENU.', C7)
    pyxel.text(58, 58, 'THIS MODE IS PLAYER VS PLAYER ARENA.', C10)
    pyxel.text(65, 76, 'CHOOSE PLAYERS', C7)
    for n in (2, 3):
        x = 106 + (n - 2) * 72
        selected = int(getattr(self, 'mp_player_count', 2)) == n
        pyxel.rect(x, 96, 50, 34, C12 if selected else C0)
        pyxel.rectb(x, 96, 50, 34, C7)
        pyxel.text(x + 22, 109, str(n), C0 if selected else C7)
    pyxel.text(46, 144, 'POWERUPS SPAWN COLOURED ATTACKS.', C7)
    pyxel.text(55, 158, 'LEFT/RIGHT SELECT    ENTER TUTORIAL', C10)
globals()['_draw_mp_select_count_v19'] = _draw_mp_select_count_v19

def _draw_mp_tutorial_v19(self):
    pyxel.cls(C0)
    pyxel.rect(10, 8, 300, 184, C0); pyxel.rectb(10, 8, 300, 184, C7)
    pyxel.text(68, 20, 'COMPETITIVE TUTORIAL', C14)
    pyxel.text(28, 36, 'SLOWER ARENA. SHARED SCREEN. DOUBLE JUMP.', C7)
    pyxel.text(28, 48, 'EVERY PLAYER HAS SPREAD GUN: 2 HITS KILL.', C10)
    rows = [_MP19_KEYS[0]['label'], _MP19_KEYS[1]['label']]
    if int(getattr(self, 'mp_player_count', 2)) >= 3:
        rows.append(_MP19_KEYS[2]['label'])
    for i, row in enumerate(rows):
        y = 66 + i * 16
        col = _MP19_COLORS[i]
        pyxel.rect(30, y - 4, 244, 13, C0); pyxel.rectb(30, y - 4, 244, 13, col)
        pyxel.text(40, y, _MP25_COLOR_NAMES[i] + '  ' + row, col)
    pyxel.rect(30, 118, 252, 34, C0); pyxel.rectb(30, 118, 252, 34, C10)
    pyxel.text(38, 126, 'POWERUPS: HUNTER DRONES, GAS CLOUDS,', C7)
    pyxel.text(38, 138, 'MISSILE STRIKES, MINES. YOUR COLOUR = YOUR ATTACK.', C7)
    _mp24_draw_crown(42, 160, C10)
    pyxel.text(62, 160, 'CROWN = EMPIRE. CROWN PLAYER NEEDS 1 MORE WIN.', C10)
    pyxel.text(82, 180, 'PRESS ENTER TO START', C10 if (pyxel.frame_count // 12) % 2 == 0 else C7)
globals()['_draw_mp_tutorial_v19'] = _draw_mp_tutorial_v19

def _draw_mp_player_v19(p, cam_x, country_key):
    sx, sy = int(p['x'] - cam_x), int(p['y'])
    idx = p['idx']; col = _MP19_COLORS[idx % len(_MP19_COLORS)]
    if not p.get('alive'):
        pyxel.text(sx, sy + 8, 'KO', C8); return
    if p.get('invuln', 0) > 0 and (pyxel.frame_count // 4) % 2 == 0:
        pyxel.circb(sx + 8, sy + 11, 14, C7)
    face = p.get('facing', 1)
    step = 1 if (p.get('on_ground') and abs(p.get('vx', 0)) > .1 and (p.get('walk', 0) // 5) % 2 == 0) else -1
    pyxel.rect(sx + 1 - face, sy + 9, 6, 12, C1)
    pyxel.rect(sx + 11 - face, sy + 9, 5, 12, C1)
    pyxel.rect(sx + 3, sy + 7, 11, 15, col)
    pyxel.rectb(sx + 3, sy + 7, 11, 15, C7)
    pyxel.rect(sx + 2, sy + 1, 13, 8, col)
    pyxel.rectb(sx + 2, sy + 1, 13, 8, C7)
    pyxel.rect(sx + (10 if face > 0 else 4), sy + 4, 4, 2, C0)
    pyxel.pset(sx + (12 if face > 0 else 5), sy + 4, C7)
    pyxel.rect(sx + 4, sy + 22, 4, 4 + step, C4)
    pyxel.rect(sx + 10, sy + 22, 4, 4 - step, C4)
    pyxel.line(sx + 8, sy + 7, sx + 8, sy + 21, C7)
    pyxel.text(sx + 1, sy - 9, 'P' + str(idx + 1), col)
    if p.get('empire'):
        _mp24_draw_crown(sx + 1, sy - 20, C10)
globals()['_draw_mp_player_v19'] = _draw_mp_player_v19

def _mp25_draw_powerup(pu, cam_x):
    sx, sy = int(pu['x'] - cam_x), int(pu['y'])
    col = {'drone': C12, 'gas': C10, 'strike': C8, 'mine': C14}.get(pu.get('kind'), C7)
    pyxel.circb(sx, sy, 8 + (pyxel.frame_count // 8) % 2, col)
    pyxel.rect(sx - 5, sy - 5, 10, 10, col)
    pyxel.rectb(sx - 6, sy - 6, 12, 12, C7)
    pyxel.text(sx - 2, sy - 3, '?', C0)

def _mp24_draw_hazard(h, cam_x):
    sx, sy = int(h['x'] - cam_x), int(h['y'])
    k = h.get('kind')
    col = _mp25_col(h.get('owner'))
    if k in ('missile', 'owner_missile'):
        pyxel.rect(sx - 3, sy - 9, 6, 14, C8 if h.get('owner') is None else col); pyxel.pset(sx, sy - 10, C7)
        pyxel.line(sx - 2, sy + 5, sx, sy + 10, C10); pyxel.line(sx + 2, sy + 5, sx, sy + 10, C10)
    elif k == 'bomb':
        pyxel.circ(sx, sy, 5, C4); pyxel.circ(sx, sy, 3, C8); pyxel.pset(sx, sy - 5, C10)
    elif k in ('drone', 'hunter'):
        pyxel.rect(sx - 11, sy - 6, 22, 12, C0); pyxel.rectb(sx - 11, sy - 6, 22, 12, col if h.get('owner') is not None else C12)
        pyxel.rect(sx - 7, sy - 3, 14, 6, col if h.get('owner') is not None else C5)
        pyxel.circ(sx - 14, sy, 3, C7); pyxel.circ(sx + 14, sy, 3, C7)
    elif k == 'gas':
        r = int(h.get('r', 14))
        for rr in range(r, max(2, r - 12), -5):
            pyxel.circb(sx + random.randint(-1,1), sy + random.randint(-1,1), rr, col)
        pyxel.text(sx - 8, sy - 3, 'GAS', C0)
    elif k == 'turret':
        pyxel.rect(sx - 8, sy, 16, 12, C4); pyxel.rectb(sx - 8, sy, 16, 12, C8); pyxel.line(sx, sy, sx + 12, sy - 7, C7)
    else:
        pyxel.circ(sx, sy, 5, col if h.get('owner') is not None else C5); pyxel.circb(sx, sy, 5, C8); pyxel.text(sx - 2, sy - 12, '!', C8)
globals()['_mp24_draw_hazard'] = _mp24_draw_hazard

def _draw_mp_arena_v19(self):
    self.draw_background()
    pyxel.camera(int(getattr(self, 'shake_x', 0)), int(getattr(self, 'shake_y', 0)))
    self.terrain.draw(self.cam_x, _mp20_difficulty(self))
    for pu in getattr(self, 'mp_powerups', []):
        _mp25_draw_powerup(pu, self.cam_x)
    for h in getattr(self, 'mp_arena_hazards', []):
        _mp24_draw_hazard(h, self.cam_x)
    for b in getattr(self, 'mp_arena_bullets', []):
        sx, sy = int(b['x'] - self.cam_x), int(b['y'])
        col = _mp25_col(b.get('owner'))
        pyxel.line(sx - int(sign(b['vx']) * 8), sy, sx, sy, col)
        pyxel.circ(sx, sy, 2, col); pyxel.pset(sx, sy, C7)
    country_key = self.current_country()['key']
    for p in getattr(self, 'mp_arena_players', []):
        _draw_mp_player_v19(p, self.cam_x, country_key)
    for e in self.effects: e.draw(self.cam_x)
    for pa in self.particles: pa.draw(self.cam_x)
    for t in self.texts: t.draw(self.cam_x)
    pyxel.camera(0, 0)
    pyxel.rect(0, 0, SCREEN_W, 27, C0); pyxel.rectb(0, 0, SCREEN_W, 27, C5)
    pyxel.text(6, 5, 'COMP ROUND ' + str(getattr(self, 'mp_round', 1)), C14)
    for p in getattr(self, 'mp_arena_players', []):
        x = 76 + p['idx'] * 80
        col = _mp25_col(p['idx'])
        pyxel.text(x, 5, 'P' + str(p['idx'] + 1) + ' ' + _v19_survival_km_text(int(p.get('dist', 0))), C10 if p.get('empire') else col)
        for hp_i in range(3):
            px = x + hp_i * 8
            pyxel.rectb(px, 15, 6, 6, col)
            if hp_i < max(0, int(p.get('hp', 0))):
                pyxel.rect(px + 1, 16, 4, 4, col)
        if p.get('empire'):
            _mp24_draw_crown(x + 34, 14, C10)
globals()['_draw_mp_arena_v19'] = _draw_mp_arena_v19

# ---------------- V26: requested cleanup: stable quakes, clean menu, MP setup, simple bullets, harder boss ----------------
FINAL_BUILD_TAG = 'v26-clean-menu-stable-quake-boss-mp'

# Keep earthquake visuals dangerous without stacking enough terrain/text/hit-stop to slow the game.
def _v26_quake_like(game):
    return bool(
        getattr(game, 'v19_quake_level', False)
        or getattr(game, 'v19_quake_warning_timer', 0) > 0
        or getattr(game, 'v19_quake_active_timer', 0) > 0
        or getattr(game, 'earthquake_warning_timer', 0) > 0
        or getattr(game, 'earthquake_timer', 0) > 0
        or getattr(game, 'nuke_aftershock_timer', 0) > 0
    )

def _v26_prune_terrain(game, softer=True):
    try:
        cx = getattr(game, 'cam_x', 0) + SCREEN_W // 2
        craters = list(getattr(game.terrain, 'craters', []))
        uplifts = list(getattr(game.terrain, 'uplifts', []))
        crater_cap = 42 if softer else 50
        uplift_cap = 36 if softer else 46
        if len(craters) > crater_cap:
            craters.sort(key=lambda c: abs(c.get('x', 0) - cx))
            game.terrain.craters = sorted(craters[:crater_cap], key=lambda c: c.get('x', 0))
        if len(uplifts) > uplift_cap:
            uplifts.sort(key=lambda u: abs(u.get('x', 0) - cx))
            game.terrain.uplifts = sorted(uplifts[:uplift_cap], key=lambda u: u.get('x', 0))
        # Red quake markers are useful, but too many FlashTexts also make the frame feel like slow-mo.
        game.texts = [t for t in getattr(game, 'texts', []) if getattr(t, 'text', '') != '!' or t.life > 8][-45:]
    except Exception:
        pass

globals()['_v24_prune_terrain'] = _v26_prune_terrain
globals()['_v25_prune_terrain'] = _v26_prune_terrain

# Less dense quake shaping: still cracked, but no huge terrain pile-up during/after quakes.
def _v26_shape_quake_span(game, start, end, seed):
    rng = random.Random(seed)
    x = start + rng.randint(55, 120)
    last_trench = -999999
    while x < end:
        x += rng.randint(108, 178)
        if x >= end:
            break
        if rng.random() < 0.70 and abs(x - last_trench) > 118:
            _v25_add_quake_trench(game.terrain, x, rng)
            try:
                _v24_mark_crack(game, x, rng)
            except Exception:
                pass
            last_trench = x
            if rng.random() < 0.66:
                _v25_add_quake_uplift(game.terrain, x + rng.choice([-1, 1]) * rng.randint(42, 82), rng)
        else:
            _v25_add_quake_uplift(game.terrain, x + rng.randint(-38, 52), rng)
        if rng.random() < 0.34:
            _v25_add_quake_uplift(game.terrain, x + rng.randint(-70, 82), rng)
    _v26_prune_terrain(game, softer=False)

globals()['_v25_shape_quake_span'] = _v26_shape_quake_span

def _v19_shape_whole_quake_map(game):
    if '_v20_quake_state' in globals():
        _v20_quake_state(game)
    if _v20_boss_active(game) or not getattr(game, 'v19_quake_level', False) or getattr(game, 'v19_quake_shaped', False):
        return
    try:
        length = _v19_level_len(game)
    except Exception:
        length = LEVEL_DISTANCE
    start = int(getattr(game, 'checkpoint_x', 40)) + max(220, int(length * 0.18))
    end = int(getattr(game, 'checkpoint_x', 40)) + length - 90
    _v26_shape_quake_span(game, start, end, int(getattr(game, 'v19_quake_seed', 0)) + int(getattr(game, 'level', 1)) * 4339)
    game.v19_quake_shaped = True
    game.v19_quake_active_timer = FPS * 8
    game.add_shake(8, 4)
    _v26_prune_terrain(game, softer=False)
globals()['_v19_shape_whole_quake_map'] = _v19_shape_whole_quake_map

def _v20_shape_survival_quake_map(game):
    if '_v20_quake_state' in globals():
        _v20_quake_state(game)
    if _v20_boss_active(game) or getattr(game, 'v19_quake_shaped', False):
        return
    game.v20_survival_quake_serial = getattr(game, 'v20_survival_quake_serial', 0) + 1
    start = int(game.cam_x + SCREEN_W + 120)
    end = start + 2200
    _v26_shape_quake_span(game, start, end, int(getattr(game, 'v19_quake_seed', 0)) + game.v20_survival_quake_serial * 4241)
    game.v19_quake_shaped = True
    game.v19_quake_active_timer = FPS * 8
    game.add_shake(8, 4)
    _v26_prune_terrain(game, softer=False)
globals()['_v20_shape_survival_quake_map'] = _v20_shape_survival_quake_map

_old_update_playing_v26_speed = Game.update_playing
def _update_playing_v26_speed(self):
    if _v26_quake_like(self):
        self.v26_no_slowmo_timer = FPS
    if getattr(self, 'v26_no_slowmo_timer', 0) > 0:
        self.hitstop_timer = 0
        self.v26_no_slowmo_timer -= 1
    result = _old_update_playing_v26_speed(self)
    if _v26_quake_like(self):
        self.v26_no_slowmo_timer = FPS
        self.hitstop_timer = 0
    if getattr(self, 'v26_no_slowmo_timer', 0) > 0:
        self.hitstop_timer = 0
    if pyxel.frame_count % 20 == 0 or _v26_quake_like(self):
        _v26_prune_terrain(self, softer=not _v26_quake_like(self))
    return result
Game.update_playing = _update_playing_v26_speed

# Simple, readable bullets only; removes the country-symbol shapes that could look like birds/eagles.
def _bullet_draw_v26_simple(self, cam_x):
    sx1 = int(getattr(self, 'px', self.x) - cam_x)
    sy1 = int(getattr(self, 'py', self.y))
    sx2 = int(self.x - cam_x)
    sy2 = int(self.y)
    if sx2 < -40 or sx2 > SCREEN_W + 40 or sy2 < -40 or sy2 > SCREEN_H + 40:
        return
    col = getattr(self, 'color', C10 if getattr(self, 'friendly', True) else C8)
    if getattr(self, 'friendly', True):
        tail = 8 if sx2 >= sx1 else -8
        pyxel.line(sx2 - tail, sy2, sx2, sy2, col)
        pyxel.circ(sx2, sy2, max(1, int(getattr(self, 'radius', 2))), col)
        pyxel.pset(sx2, sy2, C7)
    else:
        tail = 6 if sx2 >= sx1 else -6
        pyxel.line(sx2 - tail, sy2, sx2, sy2, C8)
        pyxel.circ(sx2, sy2, max(1, int(getattr(self, 'radius', 2))), C8)
        pyxel.pset(sx2, sy2, C10)
Bullet.draw = _bullet_draw_v26_simple

# Boss: 2.5x health and much more frequent shooting, without removing existing lasers.
_old_boss_init_v26 = BossEnemy.__init__
def _boss_init_v26(self, x):
    _old_boss_init_v26(self, x)
    base_hp = max(1, int(getattr(self, 'max_hp', getattr(self, 'hp', 70))))
    self.max_hp = int(base_hp * 2.5)
    self.hp = self.max_hp
    self.fire_cd = min(int(getattr(self, 'fire_cd', FPS)), FPS)
    self.v26_burst_cd = random.randint(24, 40)
BossEnemy.__init__ = _boss_init_v26

_old_boss_update_v26 = BossEnemy.update
def _boss_update_v26(self, game):
    # Make the standard laser cycle come back faster.
    if getattr(self, 'laser_timer', 0) <= 0 and getattr(self, 'laser_charge', 0) <= 0 and getattr(self, 'super_laser_timer', 0) <= 0 and getattr(self, 'v25_cinema_timer', 0) <= 0:
        if getattr(self, 'fire_cd', 0) > 34:
            self.fire_cd -= 2
    _old_boss_update_v26(self, game)
    if not getattr(self, 'alive', True):
        return
    if getattr(game, 'state', '') != 'playing':
        return
    if getattr(self, 'laser_charge', 0) > 0 and self.laser_charge > 24:
        self.laser_charge -= 1
    if getattr(self, 'laser_timer', 0) > 0 or getattr(self, 'super_laser_timer', 0) > 0 or getattr(self, 'v25_cinema_timer', 0) > 0:
        return
    self.v26_burst_cd = getattr(self, 'v26_burst_cd', 30) - 1
    if self.v26_burst_cd <= 0:
        low = self.hp < self.max_hp * 0.45
        self.v26_burst_cd = random.randint(16, 27) if low else random.randint(22, 36)
        dx = (game.player.x + PLAYER_W / 2) - (self.x + self.w / 2)
        dy = (game.player.y + PLAYER_H / 2) - (self.y + 26)
        ang = math.atan2(dy, dx)
        offsets = [-0.24, -0.10, 0.0, 0.10, 0.24] if low else [-0.18, 0.0, 0.18]
        for off in offsets:
            sp = 2.65 + (0.25 if low else 0.0)
            game.enemy_bullets.append(Bullet(
                self.x + self.w / 2, self.y + 26,
                math.cos(ang + off) * sp,
                math.sin(ang + off) * sp,
                dmg=1, color=C8, radius=2, friendly=False, life=145
            ))
        try:
            game.effects.append(Explosion(self.x + self.w / 2, self.y + 26, 5, 'muzzle'))
        except Exception:
            pass
BossEnemy.update = _boss_update_v26

# Dash-save is still possible, but the grace window is shorter.
_old_player_update_v26_dash = Player.update
def _player_update_v26_dash(self, difficulty, dev_mode=False):
    result = _old_player_update_v26_dash(self, difficulty, dev_mode)
    if hasattr(self, 'boss_laser_dash_grace'):
        self.boss_laser_dash_grace = min(int(self.boss_laser_dash_grace), int(FPS * 0.58))
    return result
Player.update = _player_update_v26_dash

# Main menu: same sections/options, but no extra black erase rectangles. Multiplayer opens a separate setup menu.
def _v26_buy_skin_if_possible(game):
    if game.skin_index not in game.unlocked_skins:
        cost = SKIN_PRICES[game.skin_index]
        if game.coins_total >= cost:
            game.coins_total -= cost
            game.unlocked_skins.append(game.skin_index)
            game.unlocked_skins = sorted(set(game.unlocked_skins))
            game.save_data['coins_total'] = game.coins_total
            game.save_data['unlocked_skins'] = game.unlocked_skins
            save_save(game.save_data)
            sfx(0, 3)

def _update_menu_v26(self):
    _v25_mp_modes(self)
    if pyxel.btnp(pyxel.KEY_LEFT):
        self.mode_index = (self.mode_index - 1) % len(self.mode_names)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_RIGHT):
        self.mode_index = (self.mode_index + 1) % len(self.mode_names)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_Q):
        self.country_index = (self.country_index - 1) % len(COUNTRIES)
        self.player.country_index = self.country_index
        self.save_data['selected_country'] = self.country_index
        save_save(self.save_data)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_E):
        self.country_index = (self.country_index + 1) % len(COUNTRIES)
        self.player.country_index = self.country_index
        self.save_data['selected_country'] = self.country_index
        save_save(self.save_data)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_A) or pyxel.btnp(pyxel.KEY_UP):
        self.skin_index = (self.skin_index - 1) % len(Player.SKINS)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_D) or pyxel.btnp(pyxel.KEY_DOWN):
        self.skin_index = (self.skin_index + 1) % len(Player.SKINS)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_U):
        _v26_buy_skin_if_possible(self)
    if pyxel.btnp(pyxel.KEY_RETURN) and self.skin_index in self.unlocked_skins:
        self.player.skin_index = self.skin_index
        self.player.country_index = self.country_index
        if self.is_multiplayer():
            self.state = 'mp_setup'
            self.mp_player_count = int(clamp(getattr(self, 'mp_player_count', 2), 2, 3))
            self.mp_mode_index = int(clamp(getattr(self, 'mp_mode_index', 0), 0, 1))
            stop_story_music()
        else:
            self.reset_run()
Game.update_menu = _update_menu_v26

def _draw_menu_v26(self):
    _v25_mp_modes(self)
    pyxel.cls(C0)
    self.draw_background()
    pyxel.camera(0, 0)
    pyxel.rect(18, 12, 284, 176, C0)
    pyxel.rectb(18, 12, 284, 176, C7)
    pyxel.text(88, 22, 'SUPREME LEADER SURVIVAL', C7)
    pyxel.text(108, 32, 'COMMAND SELECTION', C14)

    pyxel.rect(32, 48, 256, 24, C1)
    pyxel.rectb(32, 48, 256, 24, C12)
    pyxel.text(40, 56, 'COUNTRY', C7)
    draw_flag(self.current_country()['key'], 102, 54, 20, 12, pyxel.frame_count * 0.15)
    pyxel.text(130, 57, self.current_country()['name'], C10)
    pyxel.text(224, 57, str(self.country_index + 1) + '/' + str(len(COUNTRIES)), C6)
    pyxel.text(252, 57, 'Q/E', C14)

    pyxel.rect(32, 78, 130, 96, C1)
    pyxel.rectb(32, 78, 130, 96, C12)
    if '_draw_skin_preview' in globals():
        _draw_skin_preview(81, 112, self.skin_index, self.current_country()['key'], empire=False)
    else:
        self.player.draw(0)
    pyxel.text(42, 86, 'SKIN HANGAR', C14)
    pyxel.text(42, 161, 'A/D BROWSE', C6)

    pyxel.rect(170, 78, 118, 96, C1)
    pyxel.rectb(170, 78, 118, 96, C12)
    skin = Player.SKINS[self.skin_index]
    pyxel.text(178, 86, skin['name'][:18], skin.get('accent', C10))
    pyxel.text(178, 100, 'BODY  ' + str(skin['body']), skin['body'])
    pyxel.text(178, 112, 'CAPE  ' + str(skin['cape']), skin['cape'])
    pyxel.text(178, 124, 'VISOR ' + str(skin['visor']), skin['visor'])
    if self.skin_index in self.unlocked_skins:
        pyxel.text(178, 140, 'STATUS: UNLOCKED', C3)
        if self.is_multiplayer():
            pyxel.text(178, 152, 'ENTER: MP SETUP', C10)
        else:
            pyxel.text(178, 152, 'READY FOR DEPLOYMENT', C7)
    else:
        cost = SKIN_PRICES[self.skin_index]
        pyxel.text(178, 140, 'STATUS: LOCKED', C8)
        pyxel.text(178, 152, 'U TO UNLOCK  ' + str(cost) + '$', C10)

    coin_txt = 'COINS ' + str(getattr(self, 'coins_total', 0)) + '$'
    pyxel.text(SCREEN_W - len(coin_txt) * 4 - 10, 7, coin_txt, C10)
    pyxel.text(40, 178, 'LEFT/RIGHT MODE  ENTER START', C6)
    x = 46
    for i, name in enumerate(self.mode_names):
        selected = i == self.mode_index
        w = 78 if len(name) < 10 else 96
        pyxel.rect(x, 182, w, 12, C12 if selected else C0)
        pyxel.rectb(x, 182, w, 12, C7)
        pyxel.text(x + 4, 185, name[:14], C0 if selected else C7)
        x += w + 6
Game.draw_menu = _draw_menu_v26

# Separate multiplayer setup: choose players + mode, then tutorial, then play.
def _mp26_mode_name(game):
    _v25_mp_modes(game)
    return 'COMPETITIVE' if int(getattr(game, 'mp_mode_index', 0)) == 1 else 'SURVIVAL ROTATION'

def _mp19_setup_select(self):
    _v25_mp_modes(self)
    self.mp_player_count = int(clamp(getattr(self, 'mp_player_count', 2), 2, 3))
    self.mp_mode_index = int(clamp(getattr(self, 'mp_mode_index', 0), 0, 1))
    self.mp_round = 1
    self.mp_empire_players = set()
    self.mp_match_winner = None
    self.mp_round_winner = None
    self.mp_round_message = ''
    self.mp_scores = [0 for _ in range(3)]
    self.state = 'mp_setup'
    stop_story_music()
globals()['_mp19_setup_select'] = _mp19_setup_select

def _update_mp_setup_v26(self):
    _v25_mp_modes(self)
    if pyxel.btnp(pyxel.KEY_LEFT) or pyxel.btnp(pyxel.KEY_A):
        self.mp_player_count = max(2, int(getattr(self, 'mp_player_count', 2)) - 1)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_RIGHT) or pyxel.btnp(pyxel.KEY_D):
        self.mp_player_count = min(3, int(getattr(self, 'mp_player_count', 2)) + 1)
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_W) or pyxel.btnp(pyxel.KEY_S) or pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.KEY_DOWN):
        self.mp_mode_index = (int(getattr(self, 'mp_mode_index', 0)) + 1) % 2
        sfx(1, 4)
    if pyxel.btnp(pyxel.KEY_RETURN):
        self.state = 'mp_tutorial'
        self.intro_timer = 0
        stop_story_music()
    if pyxel.btnp(pyxel.KEY_ESCAPE):
        self.state = 'menu'

def _draw_mp_setup_v26(self):
    _v25_mp_modes(self)
    pyxel.cls(C0)
    self.draw_background()
    pyxel.camera(0, 0)
    pyxel.rect(22, 16, 276, 170, C1)
    pyxel.rectb(22, 16, 276, 170, C12)
    pyxel.text(88, 30, 'MULTIPLAYER SETUP', C14)
    pyxel.text(50, 49, 'CHOOSE PLAYERS', C7)
    for n in (2, 3):
        x = 92 + (n - 2) * 82
        selected = int(getattr(self, 'mp_player_count', 2)) == n
        pyxel.rect(x, 64, 58, 32, C12 if selected else C0)
        pyxel.rectb(x, 64, 58, 32, C7)
        pyxel.text(x + 24, 77, str(n), C0 if selected else C7)
    pyxel.text(48, 109, 'CHOOSE MODE', C7)
    labels = ['SURVIVAL ROTATION', 'COMPETITIVE']
    for i, label in enumerate(labels):
        y = 124 + i * 18
        selected = int(getattr(self, 'mp_mode_index', 0)) == i
        pyxel.rect(70, y - 4, 180, 13, C12 if selected else C0)
        pyxel.rectb(70, y - 4, 180, 13, C7)
        pyxel.text(82, y, label, C0 if selected else C7)
    pyxel.text(42, 166, 'A/D PLAYERS   W/S MODE   ENTER TUTORIAL', C10)
    pyxel.text(112, 177, 'ESC BACK', C6)

def _update_mp_tutorial_v26(self):
    if pyxel.btnp(pyxel.KEY_RETURN):
        _v25_mp_modes(self)
        if int(getattr(self, 'mp_mode_index', 0)) == 1:
            self.mp_round = 1
            self.mp_empire_players = set()
            self.mp_match_winner = None
            _mp19_start_round(self)
        else:
            self.mp_scores = [0 for _ in range(int(getattr(self, 'mp_player_count', 2)))]
            self.mp_current_player = 0
            self.mp_segment_progress = 0
            self.mp_campaign_segments_left = CAMPAIGN_LEVELS
            self.reset_multiplayer_session()
            self.state = 'playing'
            stop_story_music()
    if pyxel.btnp(pyxel.KEY_ESCAPE):
        self.state = 'mp_setup'
globals()['_update_mp_tutorial_v19'] = _update_mp_tutorial_v26

def _draw_mp_tutorial_v26(self):
    mode = _mp26_mode_name(self)
    pyxel.cls(C0)
    pyxel.rect(16, 10, 288, 180, C1)
    pyxel.rectb(16, 10, 288, 180, C7)
    pyxel.text(82, 24, 'MULTIPLAYER TUTORIAL', C14)
    pyxel.text(34, 42, 'MODE: ' + mode, C10)
    rows = [_MP19_KEYS[0]['label'], _MP19_KEYS[1]['label']]
    if int(getattr(self, 'mp_player_count', 2)) >= 3:
        rows.append(_MP19_KEYS[2]['label'])
    for i, row in enumerate(rows):
        y = 62 + i * 15
        pyxel.text(38, y, row, _MP19_COLORS[i % len(_MP19_COLORS)])
    if int(getattr(self, 'mp_mode_index', 0)) == 1:
        info = [
            'ARENA: LAST / FARTHEST PLAYER WINS ROUND.',
            'POWERUPS SPAWN COLOURED ATTACKS.',
            'ROUND WINNER GETS EMPIRE CROWN.',
            'CROWN PLAYER NEEDS ONE MORE WIN.',
        ]
    else:
        info = [
            'SURVIVAL ROTATION: PLAYERS TAKE TURNS.',
            'SCORE IS SAVED AFTER EACH PLAYER FALLS.',
            'SURVIVE FARTHER TO BEAT THE OTHER PLAYERS.',
            'NO EXTRA SETUP AFTER THIS SCREEN.',
        ]
    for i, line in enumerate(info):
        pyxel.text(36, 116 + i * 12, line[:56], C7 if i != 1 else C10)
    pyxel.text(82, 176, 'PRESS ENTER TO PLAY', C10 if (pyxel.frame_count // 12) % 2 == 0 else C7)
globals()['_draw_mp_tutorial_v19'] = _draw_mp_tutorial_v26

_old_game_update_v26_mp_setup = Game.update
def _game_update_v26_mp_setup(self):
    if getattr(self, 'state', '') == 'mp_setup':
        _update_mp_setup_v26(self)
        return
    return _old_game_update_v26_mp_setup(self)
Game.update = _game_update_v26_mp_setup

_old_game_draw_v26_mp_setup = Game.draw
def _game_draw_v26_mp_setup(self):
    if getattr(self, 'state', '') == 'mp_setup':
        _draw_mp_setup_v26(self)
        return
    return _old_game_draw_v26_mp_setup(self)
Game.draw = _game_draw_v26_mp_setup


# ---------------- V27: restore custom bullets + make competitive less abrupt ----------------
FINAL_BUILD_TAG = 'v27-restore-bullets-fairer-competitive'

# Restore the custom country / empire bullet art chain. V26's simple bullet override was too destructive.
try:
    if '_bullet_draw_v24' in globals():
        Bullet.draw = _bullet_draw_v24
    elif '_bullet_draw_v19_symbol' in globals():
        Bullet.draw = _bullet_draw_v19_symbol
    elif '_bullet_draw_v16_detail' in globals():
        Bullet.draw = _bullet_draw_v16_detail
except Exception:
    pass

def _mp27_player_bullet_style(game, p):
    key = game.current_country()['key']
    try:
        if p.get('empire') and '_empire_bullet_style_v15' in globals():
            return _empire_bullet_style_v15(key)
        if '_v16_bullet_style_for_key' in globals():
            return _v16_bullet_style_for_key(key)
    except Exception:
        pass
    return None

_old_mp19_start_round_v27 = globals().get('_mp19_start_round')
def _mp19_start_round(game):
    count = int(clamp(getattr(game, 'mp_player_count', 2), 2, 3))
    if not isinstance(getattr(game, 'mp_round_wins', None), list) or len(getattr(game, 'mp_round_wins', [])) != count:
        game.mp_round_wins = [0 for _ in range(count)]
    if _old_mp19_start_round_v27:
        _old_mp19_start_round_v27(game)
    for p in getattr(game, 'mp_arena_players', []):
        p['max_hp'] = 5
        p['hp'] = 5
        p['invuln'] = max(int(p.get('invuln', 0)), FPS * 2)
        p['shot_cd'] = 18
    game.mp_arena_spawn_cd = max(int(getattr(game, 'mp_arena_spawn_cd', 60)), FPS * 2)
    game.mp_powerup_cd = max(int(getattr(game, 'mp_powerup_cd', FPS * 4)), FPS * 3)
globals()['_mp19_start_round'] = _mp19_start_round

_old_mp19_update_player_v27 = globals().get('_mp19_update_player')
def _mp19_update_player(game, p):
    before = len(getattr(game, 'mp_arena_bullets', []))
    if _old_mp19_update_player_v27:
        _old_mp19_update_player_v27(game, p)
    style = _mp27_player_bullet_style(game, p)
    for b in getattr(game, 'mp_arena_bullets', [])[before:]:
        if b.get('owner') == p.get('idx'):
            if style:
                b['style'] = style
            b['dmg'] = 1
            b['life'] = max(int(b.get('life', 0)), 70)
globals()['_mp19_update_player'] = _mp19_update_player

_old_mp19_player_hit_v27 = globals().get('_mp19_player_hit')
def _mp19_player_hit(game, p, dmg=1):
    dmg = max(1, min(int(dmg), 2))
    if _old_mp19_player_hit_v27:
        return _old_mp19_player_hit_v27(game, p, dmg)
globals()['_mp19_player_hit'] = _mp19_player_hit

def _update_mp_arena_v19(self):
    self.mp_arena_timer = getattr(self, 'mp_arena_timer', 0) + 1
    self.field_order = None
    self.v19_quake_level = False
    self.nuke_aftershock_timer = 0
    for p in list(getattr(self, 'mp_arena_players', [])):
        _mp19_update_player(self, p)
    _mp24_shared_camera(self)
    _mp19_update_bullets_and_hazards(self)
    _mp24_shared_camera(self)
    for e in self.effects:
        e.update()
    for pa in self.particles:
        pa.update()
    for t in self.texts:
        t.update()
    self.effects = [e for e in self.effects if e.alive]
    self.particles = [p for p in self.particles if p.alive]
    self.texts = [t for t in self.texts if t.alive][-35:]
    self.update_shake()
    live_count = sum(1 for p in self.mp_arena_players if p.get('alive'))
    if live_count <= 1 and self.mp_arena_timer > FPS * 12:
        _mp19_finish_round(self)
    elif self.mp_arena_timer > FPS * 120:
        _mp19_finish_round(self)
globals()['_update_mp_arena_v19'] = _update_mp_arena_v19

def _mp19_finish_round(game):
    players = getattr(game, 'mp_arena_players', [])
    if not players:
        return
    winner_p = max(players, key=lambda p: (p.get('alive', False), p.get('survive_frames', 0), p.get('hp', 0), -p.get('idx', 0)))
    winner = int(winner_p['idx'])
    count = len(players)
    if not isinstance(getattr(game, 'mp_round_wins', None), list) or len(getattr(game, 'mp_round_wins', [])) != count:
        game.mp_round_wins = [0 for _ in range(count)]
    game.mp_round_wins[winner] += 1
    game.mp_round_winner = winner
    game.mp_scores = [int(p.get('survive_frames', 0) * .70) for p in players]
    wins_txt = '  '.join('P' + str(i + 1) + ':' + str(w) for i, w in enumerate(game.mp_round_wins))
    if game.mp_round_wins[winner] >= 2:
        game.mp_match_winner = winner
        game.mp_round_message = 'P' + str(winner + 1) + ' WINS THE MATCH'
        game.state = 'mp_final'
    else:
        game.mp_round_message = 'P' + str(winner + 1) + ' WINS ROUND  -  FIRST TO 2   ' + wins_txt
        game.state = 'mp_round_result'
    play_story_music()
globals()['_mp19_finish_round'] = _mp19_finish_round

def _draw_mp_arena_v19(self):
    self.draw_background()
    pyxel.camera(int(getattr(self, 'shake_x', 0)), int(getattr(self, 'shake_y', 0)))
    self.terrain.draw(self.cam_x, _mp20_difficulty(self))
    for pu in getattr(self, 'mp_powerups', []):
        _mp25_draw_powerup(pu, self.cam_x)
    for h in getattr(self, 'mp_arena_hazards', []):
        _mp24_draw_hazard(h, self.cam_x)
    country_key = self.current_country()['key']
    for b in getattr(self, 'mp_arena_bullets', []):
        sx, sy = int(b['x'] - self.cam_x), int(b['y'])
        owner = b.get('owner')
        owner_player = None
        for p in getattr(self, 'mp_arena_players', []):
            if p.get('idx') == owner:
                owner_player = p
                break
        style = b.get('style')
        if style in (None, 'spread') and owner_player is not None:
            style = _mp27_player_bullet_style(self, owner_player)
        if style and '_draw_mini_bullet_thing_v16' in globals():
            _draw_mini_bullet_thing_v16(style, sx, sy, b.get('seed', 0))
        else:
            col = _mp25_col(owner)
            pyxel.line(sx - int(sign(b['vx']) * 8), sy, sx, sy, col)
            pyxel.circ(sx, sy, 2, col)
            pyxel.pset(sx, sy, C7)
    for p in getattr(self, 'mp_arena_players', []):
        _draw_mp_player_v19(p, self.cam_x, country_key)
    for e in self.effects:
        e.draw(self.cam_x)
    for pa in self.particles:
        pa.draw(self.cam_x)
    for t in self.texts:
        t.draw(self.cam_x)
    pyxel.camera(0, 0)
    pyxel.rect(0, 0, SCREEN_W, 29, C0)
    pyxel.rectb(0, 0, SCREEN_W, 29, C5)
    pyxel.text(6, 5, 'COMP ROUND ' + str(getattr(self, 'mp_round', 1)), C14)
    wins = getattr(self, 'mp_round_wins', [])
    for p in getattr(self, 'mp_arena_players', []):
        x = 74 + p['idx'] * 80
        col = _mp25_col(p['idx'])
        pyxel.text(x, 5, 'P' + str(p['idx'] + 1) + ' W' + str(wins[p['idx']] if p['idx'] < len(wins) else 0), C10 if p.get('empire') else col)
        max_hp = max(1, int(p.get('max_hp', 5)))
        for hp_i in range(max_hp):
            px = x + hp_i * 7
            pyxel.rectb(px, 16, 5, 5, col)
            if hp_i < max(0, int(p.get('hp', 0))):
                pyxel.rect(px + 1, 17, 3, 3, col)
        if p.get('empire'):
            _mp24_draw_crown(x + 38, 14, C10)
globals()['_draw_mp_arena_v19'] = _draw_mp_arena_v19

def _draw_mp_round_result_v19(self):
    pyxel.cls(C0)
    pyxel.rect(28, 28, 264, 144, C0)
    pyxel.rectb(28, 28, 264, 144, C10)
    pyxel.text(82, 44, 'ROUND ' + str(getattr(self, 'mp_round', 1)) + ' RESULT', C14)
    pyxel.text(42, 64, str(getattr(self, 'mp_round_message', 'ROUND COMPLETE'))[:58], C7)
    wins = getattr(self, 'mp_round_wins', [])
    for i, score in enumerate(getattr(self, 'mp_scores', [])):
        y = 88 + i * 14
        round_wins = wins[i] if i < len(wins) else 0
        pyxel.text(68, y, 'P' + str(i + 1) + '  ROUNDS ' + str(round_wins) + '  DIST ' + _v19_survival_km_text(int(score)), C10 if i == getattr(self, 'mp_round_winner', -1) else C7)
    pyxel.text(72, 140, 'NO CROWN AUTO-WIN. FIRST TO 2 ROUNDS.', C14)
    pyxel.text(76, 154, 'PRESS ENTER FOR NEXT ROUND', C10 if (pyxel.frame_count // 10) % 2 == 0 else C7)
globals()['_draw_mp_round_result_v19'] = _draw_mp_round_result_v19

def _draw_mp_final_v19(self):
    pyxel.cls(C0)
    winner = int(getattr(self, 'mp_match_winner', 0))
    pyxel.rect(36, 32, 248, 136, C0)
    pyxel.rectb(36, 32, 248, 136, C14)
    pyxel.text(96, 54, 'MULTIPLAYER WINNER', C10)
    pyxel.text(132, 78, 'P' + str(winner + 1), C14)
    pyxel.text(74, 102, 'First to 2 competitive rounds.', C7)
    wins = getattr(self, 'mp_round_wins', [])
    for i, score in enumerate(getattr(self, 'mp_scores', [])):
        rw = wins[i] if i < len(wins) else 0
        pyxel.text(82, 122 + i * 10, 'P' + str(i + 1) + ': ' + str(rw) + ' rounds, ' + _v19_survival_km_text(int(score)), C10 if i == winner else C7)
    pyxel.text(92, 154, 'PRESS ENTER FOR MENU', C7)
globals()['_draw_mp_final_v19'] = _draw_mp_final_v19


if __name__ == "__main__":
    Game()