Code Sketch


yadnesh shahare
By: Mhalsakant School
Category: Art
// --- ISRO ULTIMATE MISSION: ROBOT SELECTION & ASTRONAUT CONTROLS EDITION ---
cleari(); originBottomLeft(); 

// MIDI Safe Initialization
var isGameAudioAllowed = false
try {
  setNoteInstrument(Instrument.PIANO)
  isGameAudioAllowed = true
} catch {
  case e: Exception => println("Audio/MIDI not supported on this device.")
}

def playSoundFx(p: Int, d: Int): Unit = {
  if (isGameAudioAllowed) {
    try { playNote(p, d) } catch { case _: Exception => }
  }
}

// Sound Tunes & Text
var fullJanaGanaTune = ArrayBuffer((53,300),(55,300),(57,300),(57,300),(57,300),(57,300),(57,300),(55,300),(57,300),(58,300),(57,300),(55,300),(53,300),(55,300),(53,500),(60,300),(60,300),(60,300),(58,300),(60,300),(58,300),(57,300),(55,300),(57,300),(58,500),(57,300),(55,300),(53,500),(53,300),(55,300),(57,300),(57,300),(57,300),(57,300),(57,300),(55,300),(57,300),(58,300),(57,300),(55,300),(53,300),(55,300),(53,500),(60,300),(60,300),(60,300),(58,300),(60,300),(58,300),(57,300),(55,300),(57,300),(58,500),(57,300),(55,300),(53,500))
val fullVandeMataramTune = Array((60,400),(62,400),(65,500),(62,400),(60,400),(62,500),(60,400),(62,400),(65,400),(67,400),(69,600),(69,400),(70,400),(69,400),(67,400),(65,400),(67,500),(65,400),(62,400),(60,400),(62,400),(60,600))

var (janaIndex, vandeIndex, charIndex, textDelayTimer) = (0, 0, 0, 0.0)
var (janaGanaFinished, anthemFinished, startAnthemPhase) = (false, false, false)
val fullAnthemText = "Jana-gana-mana-adhinayaka jaya he\nBharata-bhagya-vidhata.\nPunjaba-Sindhu-Gujarata-Maratha\nDravida-Utkala-Banga\nVindhya-Himachala-Yamuna-Ganga\nUchchhala-Jaladhi-taranga\nTava subha name jage, Tava subha asisa mage,\nGaye tava jaya gatha.\nJana-gana-mangala-dayaka jaya he\nBharata-bhagya-vidhata.\nJaya he, jaya he, jaya he,\nJaya jaya jaya, jaya he!\n\n--- VANDE MATARAM ---\nVande Mataram! Sujalam Suphalam\nMalayaja Shytamalam Shasya Shyamalam\nMataram! Vande Mataram!"

// --- STORY STAGES ---
var currentStoryStage = -2 
var selectedRobotType = 1 
var isRobotUnlocked = false 
var robotChallengeScore = 0
var robotChallengeTarget = 5
var (robotGamePlayerX, robotGamePlayerY) = (cwidth / 2.0, 80.0)
var (cyberEnergyX, cyberEnergyY) = (cwidth / 2.0, cheight - 150.0)
var cyberEnergyVy = -3.5

var welcomeTimer = 0.0
var letterTextTyped = 0
var letterTimer = 0.0
var titleCardTimer = 0.0

val officialLetterText = 
  "CONFIDENTIAL: INDIAN SPACE RESEARCH ORGANISATION (ISRO)\n" +
  "SUBJECT: SELECTION FOR GAGANYAAN HUMAN SPACEFLIGHT PROGRAM\n\n" +
  "Dear Commander / Candidate,\n\n" +
  "Congratulations! Based on your extraordinary skill, technical excellence,\n" +
  "and courage, you have been selected for India's prestigious Space Mission.\n\n" +
  "Your presence is immediately requested at SDSC SHAR, Sriharikota.\n" +
  "You are summoned to pilot our advanced spacecraft and represent the nation.\n\n" +
  "Click below or Press 'ACCEPTED' to report to HQ and start your mission."

def playAnthemStep(): Unit = {
  textDelayTimer += 0.04
  if (textDelayTimer >= 0.15) { textDelayTimer = 0.0; if (charIndex < fullAnthemText.length) charIndex += 1 }
  if (!janaGanaFinished) {
    if (janaIndex < fullJanaGanaTune.length) { val (n, d) = fullJanaGanaTune(janaIndex); playSoundFx(n, d); janaIndex += 1 }
    else { fullJanaGanaTune.clear(); janaGanaFinished = true }
  } else {
    if (vandeIndex < fullVandeMataramTune.length) { val (n, d) = fullVandeMataramTune(vandeIndex); playSoundFx(n, d); vandeIndex += 1 }
    else if (charIndex >= fullAnthemText.length) anthemFinished = true
  }
}

case class PlanetDetail(name: String, realName: String, surfaceCol: Color, darkSurfCol: Color, accent3D: Color, hasRings: Boolean, gameModeType: Int, infoLine1: String, infoLine2: String, infoLine3: String, infoLine4: String, infoLine5: String, infoLine6: String, infoLine7: String, infoLine8: String, quizQuestion: String, quizOptions: Array[String], correctOpt: Int)

val realSolarSystemMap = Array(
  PlanetDetail("Mercury", "Mercury (Budh)", cm.rgb(120,120,120), cm.rgb(60,60,60), cm.rgb(200,200,200), false, 2, " Gravity: 3.7 m/s� | Closest Planet to Sun", " Extreme Temp: Very hot days, freezing nights", " High Orbital Inclination & Delta-V Criteria", " Distance: 57.9 Million km | Orbit: 88 Days", " Radius: 2,439.7 km (~2.44 Million m)", " Mass: 3.301 � 10�� kg | Temp: -180�C to 430�C", " Exosphere: Thin helium & sodium trace", " Surface: Heavily cratered silicate crust", "Which planet is closest to the Sun in our Solar System?", Array("Mercury", "Venus", "Mars", "Earth"), 0),
  PlanetDetail("Venus", "Venus (Shukra)", cm.rgb(210,150,80), cm.rgb(100,70,30), cm.rgb(255,200,130), false, 3, " Gravity: 8.87 m/s� | Shukrayaan-1 Target", " Dense Atmosphere: 96.5% Carbon Dioxide", " High-Resolution Synthetic Aperture Radar", " Distance: 108.2 Million km | Orbit: 225 Days", " Radius: 6,051.8 km (~6.05 Million m)", " Mass: 4.867 � 10�? kg | Temp: ~464�C", " Pressure: 92 bar (Supercritical CO2)", " Clouds: Concentrated sulfuric acid droplets", "Which is the hottest planet in the Solar System?", Array("Venus", "Mercury", "Mars", "Jupiter"), 0),
  PlanetDetail("Earth", "Earth (Prithvi)", cm.rgb(40,130,240), cm.rgb(20,60,120), cm.rgb(100,200,255), false, 1, " Gravity: 9.81 m/s� | Our Home Planet", " Satellite: Moon (Chandrayaan-3 South Pole)", " Launch Centre: SDSC SHAR, Sriharikota", " Distance: 149.6 Million km (1 AU)", " Radius: 6,371 km (~6.37 Million m)", " Mass: 5.972 � 10�? kg | Temp: -89�C to 58�C", " Atmosphere: 78% Nitrogen, 21% Oxygen", " Biosphere: Only known life-bearing world", "How many days does Earth take to complete one orbit around the Sun?", Array("365 Days", "100 Days", "500 Days", "30 Days"), 0),
  PlanetDetail("Mars", "Mars (Mangal)", cm.rgb(200,70,40), cm.rgb(100,30,15), cm.rgb(255,130,100), false, 2, " Gravity: 3.72 m/s� | Mars Orbiter Mission (MOM)", " Launch Vehicle: PSLV-XL C25 Config", " Interplanetary Hohmann Transfer Orbit", " Distance: 227.9 Million km | Orbit: 687 Days", " Radius: 3,389.5 km (~3.39 Million m)", " Mass: 6.417 � 10�� kg | Temp: -125�C to 20�C", " Thin Atmosphere: 95% CO2, low pressure", " Topography: Olympus Mons & Valles Marineris", "Which planet is famously known as the 'Red Planet'?", Array("Mars", "Mercury", "Jupiter", "Saturn"), 0),
  PlanetDetail("Jupiter", "Jupiter (Guru)", cm.rgb(210,140,90), cm.rgb(100,60,40), cm.rgb(255,190,140), true, 1, " Gravity: 24.79 m/s� | Largest Planet", " Powerful Magnetic Field & Radiation", " Subsurface Water Ocean on Europa", " Distance: 778.5 Million km | Orbit: 11.8 Years", " Radius: 69,911 km (~69.9 Million m)", " Mass: 1.898 � 10�? kg | Temp: ~ -110�C", " Composition: 90% Hydrogen, 10% Helium", " Great Red Spot: Giant anticyclonic storm", "Which is the largest planet in our Solar System?", Array("Jupiter", "Saturn", "Neptune", "Earth"), 0),
  PlanetDetail("Saturn", "Saturn (Shani)", cm.rgb(190,170,110), cm.rgb(90,80,50), cm.rgb(240,230,160), true, 2, " Gravity: 10.44 m/s� | Ringed Planet Jewel", " Titan's Dense Atmosphere & Hydrocarbons", " Active Cryovolcanism on Enceladus", " Distance: 1.4 Billion km | Orbit: 29.5 Years", " Radius: 58,232 km (~58.2 Million m)", " Mass: 5.683 � 10�? kg | Temp: ~ -140�C", " Rings: 99% water ice with silicate dust", " Density: Less than water (0.687 g/cm�)", "Which planet is famous for its bright prominent ring system?", Array("Saturn", "Mars", "Mercury", "Venus"), 0),
  PlanetDetail("Uranus", "Uranus (Arun)", cm.rgb(100,200,220), cm.rgb(40,90,100), cm.rgb(180,240,255), true, 3, " Gravity: 8.69 m/s� | Extreme 97.77� Tilt", " Ice Giant with Methane Mantle Overlay", " Off-center Offset Magnetic Axis", " Distance: 2.87 Billion km | Orbit: 84 Years", " Radius: 25,362 km (~25.3 Million m)", " Mass: 8.681 � 10�? kg | Temp: ~ -195�C", " Atmosphere: Hydrogen, helium, methane ice", " Coldest Planetary Atmosphere in Solar System", "Which blue-green ice giant planet has an extreme axial tilt?", Array("Uranus", "Mars", "Venus", "Mercury"), 0),
  PlanetDetail("Neptune", "Neptune (Varun)", cm.rgb(50,90,220), cm.rgb(20,40,100), cm.rgb(130,170,255), true, 2, " Gravity: 11.15 m/s� | Outermost Major Planet", " Supersonic Winds: Up to 2,100 km/h", " Retrograde Orbit of Triton Moon", " Distance: 4.5 Billion km | Orbit: 165 Years", " Radius: 24,622 km (~24.6 Million m)", " Mass: 1.024 � 10�? kg | Temp: ~ -200�C", " Dynamic Atmosphere: Great Dark Spot storms", " Triton: Active nitrogen geysers observed", "Which is the farthest major planet from the Sun in our Solar System?", Array("Neptune", "Mars", "Jupiter", "Mercury"), 0)
)

val keplerSolarSystemMap = Array(
  PlanetDetail("Kepler-22b", "Kepler-22b (KOI-087.01)", cm.rgb(70,180,150), cm.rgb(30,90,70), cm.rgb(120,240,200), false, 2, " First habitable 'Super-Earth' planet candidate.", " Size: 2.4 times larger than Earth.", " Orbital Distance: 0.85 AU (127 Million km)", " Distance from Earth: ~635 Light Years", " Radius: ~15,290 km (~15.29 Million m)", " Mass: ~9.1 Earth Masses | Temp: -11�C to 22�C", " Planet Type: Potential Ocean World or Mini-Neptune", " Discovery Year: 2011 (Kepler Telescope)", "Because Kepler-22b is larger than Earth, what classification is it given?", Array("Super-Earth", "Mini-Moon", "Small Star", "Flat World"), 0),
  PlanetDetail("Kepler-16b", "Kepler-16b (Kepler-16AB b)", cm.rgb(200,160,220), cm.rgb(90,70,110), cm.rgb(245,210,255), true, 3, " Famous 'Tatooine' planet with dual suns.", " Orbits around two stars simultaneously (Circumbinary).", " Gas Giant composition (Saturn-sized).", " Distance from Earth: ~245 Light Years", " Radius: ~53,200 km (~53.2 Million m)", " Mass: ~0.33 Jupiter Mass | Temp: -70�C", " Primary Stars: K-dwarf + M-dwarf pair", " Orbit Period: 229 Days around binary center", "How many host stars does planet Kepler-16b orbit?", Array("2 Stars (Binary)", "1 Star", "5 Stars", "None"), 0),
  PlanetDetail("Kepler-186f", "Kepler-186f (KOI-571.05)", cm.rgb(140,210,110), cm.rgb(60,90,45), cm.rgb(200,255,170), false, 2, " First Earth-sized planet in Habitable Zone.", " Potential candidate for liquid surface water.", " High probability of rocky surface terrain.", " Distance from Earth: ~580 Light Years", " Radius: ~7,450 km (~7.45 Million m)", " Mass: ~1.4 Earth Mass | Orbit: 130 Days", " Host Star: Red Dwarf (M1V class)", " Insolation: Receives 32% of Earth's sunlight", "Is planet Kepler-186f located in its star's Habitable Zone?", Array("Yes", "No", "Unknown", "Never"), 0),
  PlanetDetail("Kepler-452b", "Kepler-452b (KOI-7016.01)", cm.rgb(90,140,200), cm.rgb(40,70,110), cm.rgb(160,210,255), false, 1, " Famously known as 'Earth's Cousin' (Earth 2.0).", " Orbital year length: 385 Days (Near Earth twin).", " Super-Earth Class rocky/thick atmosphere planet.", " Distance from Earth: ~1,800 Light Years", " Radius: ~10,380 km (~10.38 Million m)", " Mass: ~5 Earth Masses | Temp: ~ -8�C", " Host Star: Kepler-452 (G2V Solar Twin)", " Age: ~6 Billion Years (1.5B years older than Sun)", "What nickname was given to Kepler-452b due to its Earth-like traits?", Array("Earth's Cousin", "Moon Twin", "Sun Twin", "Mars Twin"), 0),
  PlanetDetail("Kepler-10b", "Kepler-10b (KOI-072.01)", cm.rgb(220,90,90), cm.rgb(110,40,40), cm.rgb(255,160,160), false, 3, " Extreme 'Lava World' with molten surface.", " Very close proximity to host star.", " Surface temp churns rock/lava (>1,300�C).", " Distance from Earth: ~560 Light Years", " Radius: ~9,360 km (~9.36 Million m)", " Mass: ~3.2 Earth Masses | Orbit: 20 Hours", " Tidal Locking: One side permanently molten", " Density: High silicate and iron core", "What covers the surface of Kepler-10b due to extreme heat?", Array("Molten Lava / Rock", "Ice Mountains", "Cold Water", "Green Forests"), 0),
  PlanetDetail("Kepler-62f", "Kepler-62f (KOI-701.04)", cm.rgb(80,190,210), cm.rgb(35,90,100), cm.rgb(150,245,255), false, 1, " Candidate 'Ocean World' covered by global sea.", " Approximately 40% larger than Earth.", " Host Star: K-type Orange Dwarf.", " Distance from Earth: ~1,200 Light Years", " Radius: ~8,980 km (~8.98 Million m)", " Mass: ~2.8 Earth Masses | Orbit: 267 Days", " Climate Model: Stable global ocean with greenhouse gas", " Habitable Index: Very High", "What is Kepler-62f most likely covered by?", Array("A Global Ocean", "Only Sand Deserts", "Gas Only", "Plastic"), 0),
  PlanetDetail("Kepler-90i", "Kepler-90i (KOI-351 i)", cm.rgb(230,150,60), cm.rgb(110,70,20), cm.rgb(255,210,130), false, 2, " Part of 8-planet system found by Google AI.", " Discovered via Neural Network Machine Learning.", " Sizzling rocky terrestrial surface.", " Distance from Earth: ~2,840 Light Years", " Radius: ~8,410 km (~8.41 Million m)", " Mass: ~2.5 Earth Masses | Orbit: 14.4 Days", " System Structure: Matches Solar System planet count", " Surface Temp: ~430�C", "Which technology helped discover planet Kepler-90i?", Array("Artificial Intelligence (Google AI)", "Simple Camera", "Microscope", "Newspaper"), 0),
  PlanetDetail("K2-18b", "K2-18b (EPIC 201912552 b)", cm.rgb(60,200,180), cm.rgb(20,90,80), cm.rgb(140,255,235), false, 2, " Hycean Planet with hydrogen atmosphere.", " JWST detected water vapor & methane traces.", " Potential liquid ocean beneath atmosphere.", " Distance from Earth: ~124 Light Years", " Radius: ~16,620 km (~16.62 Million m)", " Mass: ~8.6 Earth Masses | Temp: ~ -8�C", " JWST Detection: Carbon-bearing molecules (CO2, CH4)", " Potential Biosignature: DMS trace research", "What trace evidence did the Space Telescope find in K2-18b's atmosphere?", Array("Water Vapor", "Petrol", "Diesel", "Milk"), 0)
)

val starCount = 850
val starX = Array.fill(starCount)(randomDouble(0, cwidth))
val starY = Array.fill(starCount)(randomDouble(0, cheight))
val starSize = Array.fill(starCount)(randomDouble(1.0, 4.2))

var (waveAngle, chakraRotation, neonGlowPhase, globalTime) = (0.0, 0.0, 0.0, 0.0)
var currentSolarSystemId = 1
var (rotAngleX, rotAngleY) = (0.45, 0.0)
var (lastMouseX, lastMouseY) = (0.0, 0.0)
var isDraggingSolarView = false

def getActivePlanetList(): Array[String] = if (currentSolarSystemId == 1) Array("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune") else Array("Kepler-22b", "Kepler-16b", "Kepler-186f", "Kepler-452b", "Kepler-10b", "Kepler-62f", "Kepler-90i", "K2-18b")
def getActivePlanetDetailsMap(): Array[PlanetDetail] = if (currentSolarSystemId == 1) realSolarSystemMap else keplerSolarSystemMap

var selectedPlanet = "Mars"
var (planetLocked, isTimerRunning, isSpaceTravelling, isLandingOnPlanet, isLaunched, isLanded) = (false, false, false, false, false, false)
var (customAlertMessage, quizFeedback) = ("", "")
var (alertTimer, countdownTimer, spaceTravelProgress, landingProgress, doorOpeningProgress, astroWaitTimer, flagPlantProgress, landingLegProgress) = (0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
var (rocketCurrentX, rocketCurrentY) = (90.0, cheight - 170.0)

// Sequence States for Robot -> Astronaut -> Flag -> Game
var (robotOutPhase, robotReturningPhase, robotInsideDone) = (false, false, false)
var robotWalkOutDist = 0.0

var astronautOut, infoPanelShown, isFlagPlantedOnPlanet, isAstronautReturningIn, quizActive, isMiniGameActive, readyToReturnEarth = (false, false, false, false, false, false, false)
var (infoPageNumber, activeGameMode) = (1, 1)

var astroWalkToPlantX = 0.0
val plantedFlagX = (cwidth / 2.0) - 80.0
val plantedFlagY = 155.0

case class DynamicParticle(var x: Double, var y: Double, var vx: Double, var vy: Double, var life: Double, col: Color, var size: Double)
val exhaustRibbons = ArrayBuffer.empty[DynamicParticle]
val dustParticles = ArrayBuffer.empty[DynamicParticle]
val explosionParticles = ArrayBuffer.empty[DynamicParticle]
val petalShower = ArrayBuffer.fill(50)(DynamicParticle(randomDouble(0, cwidth), randomDouble(cheight, cheight + 200), randomDouble(-0.6, 0.6), randomDouble(-2.2, -1.0), 1.0, cm.rgb(255, 153, 51), randomDouble(4, 8)))

def addTricolorExhaust(x: Double, y: Double): Unit = {
  val t = globalTime * 14.0
  exhaustRibbons.append(DynamicParticle(x - 8 + math.sin(t) * 6, y, randomDouble(-1.2, 1.2), randomDouble(-4.0, -2.0), 1.0, cm.rgb(255, 153, 51), randomDouble(8.0, 15.0)))
  exhaustRibbons.append(DynamicParticle(x, y, randomDouble(-0.8, 0.8), randomDouble(-5.0, -3.0), 1.0, cm.rgb(255, 255, 255), randomDouble(10.0, 17.0)))
  exhaustRibbons.append(DynamicParticle(x + 8 + math.cos(t) * 6, y, randomDouble(-1.2, 1.2), randomDouble(-4.0, -2.0), 1.0, cm.rgb(18, 136, 7), randomDouble(8.0, 15.0)))
}

def addLandingDust(x: Double, y: Double): Unit = {
  for (_ <- 0 until 5) dustParticles.append(DynamicParticle(x + randomDouble(-35, 35), y, randomDouble(-2.8, 2.8), randomDouble(0.2, 1.8), 1.0, cm.rgb(210, 180, 140), randomDouble(4.0, 10.0)))
}

def triggerExplosion(x: Double, y: Double): Unit = {
  for (_ <- 0 until 30) {
    val gVal = randomDouble(50, 200).toInt
    explosionParticles.append(DynamicParticle(x, y, randomDouble(-7.0, 7.0), randomDouble(-7.0, 7.0), 1.0, cm.rgb(255, gVal, 0), randomDouble(5.0, 14.0)))
  }
}

def updateExhaust(): Unit = {
  exhaustRibbons.filterInPlace(_.life > 0)
  exhaustRibbons.foreach { p => p.x += p.vx + math.sin(globalTime * 10.0) * 1.5; p.y += p.vy; p.life -= 0.035; p.size += 0.4 }
  dustParticles.filterInPlace(_.life > 0)
  dustParticles.foreach { p => p.x += p.vx; p.y += p.vy; p.life -= 0.03; p.size += 0.25 }
  explosionParticles.filterInPlace(_.life > 0)
  explosionParticles.foreach { p => p.x += p.vx; p.y += p.vy; p.life -= 0.045; p.size *= 0.94 }
  petalShower.foreach { p => p.y += p.vy; p.x += math.sin(globalTime * 3 + p.y * 0.05) * 1.4; if (p.y < 0) { p.y = cheight + 20; p.x = randomDouble(0, cwidth) } }
}

var miniGameTimer = 20.0
var isMiniGameTimerRunning = false
var (devilPlayerX, devilPlayerY, devilPlayerVy, devilPlayerVx) = ((cwidth / 2.0) - 180.0, 130.0, 0.0, 0.0)
var (isGrounded, canDoubleJump, jumpKeyPressedLastFrame, isDevilKeyCollected) = (false, false, false, false)
var (trollKeyX, trollKeyY, exitDoorX) = ((cwidth / 2.0) + 140.0, 140.0, (cwidth / 2.0) - 180.0)

var (dodgerShipX, dodgerShipY) = (cwidth / 2.0, cheight / 2.0 - 50.0)
val dodgerCount = 5
var dodgerAstX = Array.fill(dodgerCount)(cwidth / 2.0)
var dodgerAstY = Array.fill(dodgerCount)(cheight / 2.0 + 150.0)
var dodgerAstSpeed = Array.fill(dodgerCount)(4.0)
var dodgerScore = 0

var (landerX, landerY, landerVy, landerVx, landerFuel) = (cwidth / 2.0, cheight / 2.0 + 60.0, 0.0, 0.0, 100.0)

def resetDodgerGame(): Unit = {
  val gx = cwidth / 2.0 - 220.0; dodgerShipX = cwidth / 2.0; dodgerScore = 0
  for (i <- 0 until dodgerCount) { dodgerAstX(i) = gx + 30.0 + randomDouble(0, 380); dodgerAstY(i) = (cheight / 2.0 + 90.0) + (i * 40.0); dodgerAstSpeed(i) = randomDouble(3.2, 5.5) }
}

def resetLanderGame(): Unit = { landerX = cwidth / 2.0; landerY = cheight / 2.0 + 60.0; landerVy = 0.2; landerVx = 0.0; landerFuel = 100.0 }

var (isReturnTimerRunning, isReturnLaunched, isLandingOnEarth, isLandedOnEarth, isParadeSceneActive, isParadeInPosition) = (false, false, false, false, false, false)
var (returnCountdownTimer, earthLandingProgress, earthAstronautWalkRightProgress) = (5.0, 0.0, 0.0)
var (astroParadeWalkX, armyParadeWalkX) = (-50.0, cwidth + 50.0)

def dieAndResetDevilLevel(msg: String): Unit = {
  playSoundFx(38, 300); triggerExplosion(devilPlayerX, devilPlayerY); customAlertMessage = s" $msg"; alertTimer = 1.8; miniGameTimer = 20.0; isMiniGameTimerRunning = true
  getActivePlanetDetailsMap().foreach { pd => if (pd.name == selectedPlanet) activeGameMode = pd.gameModeType }
  if (activeGameMode == 1) resetDodgerGame()
  else if (activeGameMode == 3) resetLanderGame()
  else {
    val gx = cwidth / 2.0 - 220.0; val gy = cheight / 2.0 - 100.0
    devilPlayerX = gx + 30.0; devilPlayerY = gy + 170.0; devilPlayerVy = 0.0; devilPlayerVx = 0.0; isGrounded = false; canDoubleJump = true
    trollKeyX = gx + 340.0; isDevilKeyCollected = false; exitDoorX = gx + 40.0
  }
}

def updateState(): Unit = {
  if (currentStoryStage == -3) {
    if (isKeyPressed(Kc.VK_LEFT) && robotGamePlayerX > 80.0) robotGamePlayerX -= 7.5
    if (isKeyPressed(Kc.VK_RIGHT) && robotGamePlayerX < cwidth - 80.0) robotGamePlayerX += 7.5

    cyberEnergyY += cyberEnergyVy
    if (cyberEnergyY < 50.0) {
      cyberEnergyY = cheight - 80.0
      cyberEnergyX = randomDouble(100.0, cwidth - 100.0)
    }

    if (math.abs(robotGamePlayerX - cyberEnergyX) < 40.0 && math.abs(robotGamePlayerY - cyberEnergyY) < 40.0) {
      robotChallengeScore += 1
      playSoundFx(78, 120)
      cyberEnergyY = cheight - 80.0
      cyberEnergyX = randomDouble(100.0, cwidth - 100.0)
      if (robotChallengeScore >= robotChallengeTarget) {
        isRobotUnlocked = true
        currentStoryStage = -1 
        playSoundFx(84, 350)
      }
    }
    return
  }

  if (currentStoryStage == -2) return
  if (currentStoryStage == -1) {
    welcomeTimer += 0.04
    if (welcomeTimer >= 7.5) { currentStoryStage = 0 }
    return
  }
  if (currentStoryStage == 0) {
    letterTimer += 0.04
    if (letterTimer >= 0.03 && letterTextTyped < officialLetterText.length) {
      letterTextTyped += 1; letterTimer = 0.0
      if (letterTextTyped % 4 == 0) playSoundFx(70, 15)
    }
    return
  }
  if (currentStoryStage == 1) {
    titleCardTimer += 0.03
    if (titleCardTimer >= 3.0) { currentStoryStage = 2 }
    return
  }

  if (startAnthemPhase) { waveAngle += 0.22; if (!anthemFinished) playAnthemStep(); return }
  waveAngle += 0.22; chakraRotation += 0.22; neonGlowPhase += 0.22; globalTime += 0.022
  updateExhaust()

  if (!isLaunched && !infoPanelShown && !quizActive && !isMiniGameActive) {
    if (isMousePressed) {
      if (!isDraggingSolarView && mouseX > 240 && mouseX < cwidth - 255) { isDraggingSolarView = true; lastMouseX = mouseX; lastMouseY = mouseY }
      else if (isDraggingSolarView) {
        val dx = mouseX - lastMouseX; val dy = mouseY - lastMouseY
        rotAngleY += dx * 0.012; rotAngleX += dy * 0.012
        lastMouseX = mouseX; lastMouseY = mouseY
      }
    } else isDraggingSolarView = false
  }

  if (alertTimer > 0) { alertTimer -= 0.04; if (alertTimer <= 0) customAlertMessage = "" }
  if (isTimerRunning && countdownTimer > 0) { countdownTimer -= 0.04; if (countdownTimer <= 0) { countdownTimer = 0.0; isTimerRunning = false; quizActive = true } }

  if (isSpaceTravelling && !isLandingOnPlanet) {
    addTricolorExhaust(rocketCurrentX, rocketCurrentY - 20); spaceTravelProgress += 0.005
    var (targetX, targetY) = (cwidth / 2.0, cheight / 2.0)
    val pList = getActivePlanetList(); val planetSpeeds = Array(0.6, 0.45, 0.35, 0.3, 0.25, 0.2, 0.15, 0.1)
    for (i <- pList.indices if pList(i) == selectedPlanet) {
      val a = globalTime * (if(i < planetSpeeds.length) planetSpeeds(i) else 0.2)
      targetX = (cwidth / 2.0 - 20.0) + math.cos(a) * ((i + 1) * 28.0); targetY = (cheight / 2.0 - 10.0) + math.sin(a) * ((i + 1) * 14.0)
    }
    val arcOffset = math.sin(spaceTravelProgress * math.Pi) * 90.0
    rocketCurrentX = (90.0 + (targetX - 90.0) * spaceTravelProgress) + arcOffset
    rocketCurrentY = ((cheight - 170.0) + (targetY - (cheight - 170.0)) * spaceTravelProgress) - arcOffset
    if (spaceTravelProgress >= 1.0) { isSpaceTravelling = false; isLandingOnPlanet = true; landingProgress = 0.0; landingLegProgress = 0.0; isLaunched = true; rocketCurrentX = cwidth / 2.0; rocketCurrentY = cheight + 100.0 }
  }

  if (isLandingOnPlanet && !isLanded) {
    addTricolorExhaust(rocketCurrentX, rocketCurrentY - 25); landingProgress += 0.004
    if (landingProgress > 0.4 && landingLegProgress < 1.0) landingLegProgress += 0.02
    if (landingProgress > 0.8) addLandingDust(rocketCurrentX, 140.0)
    rocketCurrentX = (cwidth / 2.0) + math.sin(landingProgress * math.Pi * 5) * 18.0
    rocketCurrentY = (cheight + 100.0) - ((cheight + 100.0) - 175.0) * landingProgress
    if (landingProgress >= 1.0) { isLandingOnPlanet = false; isLanded = true; rocketCurrentX = cwidth / 2.0; rocketCurrentY = 175.0; landingLegProgress = 1.0; playSoundFx(45, 250) }
  }

  if (isLanded && !isReturnLaunched && !isAstronautReturningIn && !isLandingOnEarth && !readyToReturnEarth && !infoPanelShown && !isMiniGameActive) {
    if (doorOpeningProgress < 1.0) {
      doorOpeningProgress += 0.01
    } else {
      // Sequence: 1. Robot comes out first
      if (!robotInsideDone) {
        if (!robotOutPhase && !robotReturningPhase) {
          robotOutPhase = true
        }
        if (robotOutPhase) {
          robotWalkOutDist += 2.0
          if (robotWalkOutDist >= 90.0) {
            robotOutPhase = false
            robotReturningPhase = true
          }
        }
        else if (robotReturningPhase) {
          robotWalkOutDist -= 2.0
          if (robotWalkOutDist <= 0.0) {
            robotReturningPhase = false
            robotInsideDone = true // Robot returned back into the spaceship successfully
          }
        }
      } 
      else {
        // 2. Astronaut comes out after robot returns
        astronautOut = true
        astroWaitTimer += 0.04
        if (astroWaitTimer >= 2.5) {
          if (isKeyPressed(Kc.VK_RIGHT) && astroWalkToPlantX < 160.0) astroWalkToPlantX += 2.0
          if (isKeyPressed(Kc.VK_LEFT) && astroWalkToPlantX > 0.0) astroWalkToPlantX -= 2.0
        }
        
        if (flagPlantProgress > 0.0 && flagPlantProgress < 1.0) {
          flagPlantProgress += 0.02
          if (flagPlantProgress >= 1.0) {
            isFlagPlantedOnPlanet = true
            infoPanelShown = true
          }
        }
      }
    }
  }

  if (isAstronautReturningIn) {
    if (astroWalkToPlantX > 0.0) astroWalkToPlantX -= 2.0
    else if (doorOpeningProgress > 0.0) doorOpeningProgress -= 0.02
    else { isAstronautReturningIn = false; astronautOut = false; isReturnTimerRunning = true; returnCountdownTimer = 5.0 }
  }

  if (isReturnTimerRunning && returnCountdownTimer > 0) { returnCountdownTimer -= 0.04; if (returnCountdownTimer <= 0) { returnCountdownTimer = 0.0; isReturnTimerRunning = false; isReturnLaunched = true } }
  if (isReturnLaunched && !isLandingOnEarth && !isLandedOnEarth) {
    addTricolorExhaust(rocketCurrentX, rocketCurrentY - 20); rocketCurrentY += 5.0
    if (rocketCurrentY > cheight + 100.0) { isReturnLaunched = false; isLandingOnEarth = true; earthLandingProgress = 0.0; landingLegProgress = 0.0; rocketCurrentX = cwidth / 2.0; rocketCurrentY = cheight + 100.0 }
  }

  if (isLandingOnEarth && !isLandedOnEarth) {
    addTricolorExhaust(rocketCurrentX, rocketCurrentY - 25); earthLandingProgress += 0.004
    if (earthLandingProgress > 0.4 && landingLegProgress < 1.0) landingLegProgress += 0.02
    if (earthLandingProgress > 0.8) addLandingDust(rocketCurrentX, 140.0)
    rocketCurrentX = (cwidth / 2.0) + math.sin(earthLandingProgress * math.Pi * 5) * 18.0
    rocketCurrentY = (cheight + 100.0) - ((cheight + 100.0) - 175.0) * earthLandingProgress
    if (earthLandingProgress >= 1.0) { isLandingOnEarth = false; isLandedOnEarth = true; rocketCurrentX = cwidth / 2.0; rocketCurrentY = 175.0; landingLegProgress = 1.0; doorOpeningProgress = 1.0; astronautOut = true; playSoundFx(40, 250) }
  }

  if (isLandedOnEarth && !isParadeSceneActive) { earthAstronautWalkRightProgress += 2.8; if (earthAstronautWalkRightProgress > cwidth / 2.0 + 200.0) isParadeSceneActive = true }
  if (isParadeSceneActive && !isParadeInPosition) {
    if (astroParadeWalkX < (cwidth / 2.0) - 120.0) astroParadeWalkX += 1.6
    if (armyParadeWalkX > (cwidth / 2.0) + 120.0) armyParadeWalkX -= 1.6
    if (astroParadeWalkX >= (cwidth / 2.0) - 120.0 && armyParadeWalkX <= (cwidth / 2.0) + 120.0) { isParadeInPosition = true; startAnthemPhase = true }
  }

  if (isMiniGameActive) {
    val gx = cwidth / 2.0 - 220.0; val gy = cheight / 2.0 - 100.0
    if (isMiniGameTimerRunning) { miniGameTimer -= 0.04; if (miniGameTimer <= 0.0) { miniGameTimer = 0.0; dieAndResetDevilLevel("Time Out! Mission Failed!") } }

    if (activeGameMode == 1) {
      if (isKeyPressed(Kc.VK_LEFT) && dodgerShipX > gx + 25) dodgerShipX -= 7.5
      if (isKeyPressed(Kc.VK_RIGHT) && dodgerShipX < gx + 415) dodgerShipX += 7.5
      for (i <- 0 until dodgerCount) {
        dodgerAstY(i) -= dodgerAstSpeed(i)
        if (math.sqrt(math.pow(dodgerShipX - dodgerAstX(i), 2) + math.pow(dodgerShipY - dodgerAstY(i), 2)) < 22.0) dieAndResetDevilLevel("Asteroid Impact! Ship Crashed!")
        if (dodgerAstY(i) < gy + 10) { dodgerAstY(i) = gy + 200.0; dodgerAstX(i) = gx + 30.0 + randomDouble(0, 380); dodgerAstSpeed(i) = randomDouble(3.0, 5.0); dodgerScore += 10; playSoundFx(72, 30) }
      }
      if (dodgerScore >= 80) { playSoundFx(75, 200); isMiniGameActive = false; isMiniGameTimerRunning = false; readyToReturnEarth = true; customAlertMessage = " Asteroid Belt Cleared! Return Ready!"; alertTimer = 3.5 }
    } else if (activeGameMode == 3) {
      val padX = gx + 180.0; val padY = gy + 20.0; val padW = 80.0
      landerVy -= 0.12; landerY += landerVy; landerX += landerVx
      if (isKeyPressed(Kc.VK_UP) && landerFuel > 0) { landerVy += 0.38; landerFuel -= 0.8; playSoundFx(50, 30) }
      if (isKeyPressed(Kc.VK_LEFT) && landerFuel > 0) { landerVx -= 0.15; landerFuel -= 0.3 }
      if (isKeyPressed(Kc.VK_RIGHT) && landerFuel > 0) { landerVx += 0.15; landerFuel -= 0.3 }
      if (landerX < gx + 20) landerX = gx + 20; if (landerX > gx + 420) landerX = gx + 420
      if (landerY <= gy + 30) {
        if (landerX >= padX && landerX <= padX + padW && math.abs(landerVy) < 1.8) { playSoundFx(80, 250); isMiniGameActive = false; isMiniGameTimerRunning = false; readyToReturnEarth = true; customAlertMessage = " Perfect Smooth Landing Achieved!"; alertTimer = 3.5 }
        else dieAndResetDevilLevel("Hard Impact! Lander Crashed!")
      }
    } else {
      val floorY = gy + 170.0
      if (isKeyPressed(Kc.VK_RIGHT) && devilPlayerX < gx + 410) devilPlayerX += 5.5
      if (isKeyPressed(Kc.VK_LEFT) && devilPlayerX > gx + 15) devilPlayerX -= 5.5
      val upPressedNow = isKeyPressed(Kc.VK_UP)
      if (upPressedNow && !jumpKeyPressedLastFrame) {
        if (isGrounded) { devilPlayerVy = -10.8; isGrounded = false; canDoubleJump = true; playSoundFx(60, 50) }
        else if (canDoubleJump) { devilPlayerVy = -10.8 * 0.9; canDoubleJump = false; playSoundFx(67, 50) }
      }
      jumpKeyPressedLastFrame = upPressedNow
      devilPlayerVy += 0.45; devilPlayerY += devilPlayerVy; devilPlayerX += devilPlayerVx; devilPlayerVx *= 0.90
      if (devilPlayerX < gx + 15) devilPlayerX = gx + 15; if (devilPlayerX > gx + 410) devilPlayerX = gx + 410
      if (devilPlayerY >= floorY) { devilPlayerY = floorY; devilPlayerVy = 0.0; devilPlayerVx = 0.0; isGrounded = true; canDoubleJump = true } else isGrounded = false
      if (!isDevilKeyCollected && math.abs(devilPlayerX - trollKeyX) < 25 && math.abs(devilPlayerY - trollKeyY) < 25) { isDevilKeyCollected = true; playSoundFx(80, 80) }
      if (isDevilKeyCollected && math.abs(devilPlayerX - exitDoorX) < 30 && math.abs(devilPlayerY - (gy + 140)) < 35) { playSoundFx(78, 200); isMiniGameActive = false; isMiniGameTimerRunning = false; readyToReturnEarth = true; customAlertMessage = " Rover Adventure Complete!"; alertTimer = 3.5 }
    }
  }
}

def drawParticleEffects(canvas: CanvasDraw): Unit = {
  exhaustRibbons.foreach { p => val safeSize = math.max(0.1, p.size); canvas.fill(p.col.getRed, p.col.getGreen, p.col.getBlue, math.max(0, math.min(255, (p.life * 230).toInt))); canvas.noStroke(); canvas.ellipse(p.x, p.y, safeSize, math.max(0.1, safeSize * 0.6)) }
  dustParticles.foreach { p => val safeSize = math.max(0.1, p.size); canvas.fill(p.col.getRed, p.col.getGreen, p.col.getBlue, math.max(0, math.min(255, (p.life * 150).toInt))); canvas.noStroke(); canvas.ellipse(p.x, p.y, safeSize, safeSize * 0.8) }
  explosionParticles.foreach { p => canvas.fill(p.col.getRed, p.col.getGreen, p.col.getBlue, math.max(0, math.min(255, (p.life * 255).toInt))); canvas.noStroke(); canvas.ellipse(p.x, p.y, p.size, p.size) }
}

def draw3DSphere(canvas: CanvasDraw, cx: Double, cy: Double, size: Double, baseColor: Color, lightAccent: Color, rotAngle: Double = 0.0): Unit = {
  val layers = 11; val safeSize = math.max(1.0, math.abs(size))
  for (i <- 0 until layers) {
    val factor = math.max(0.01, 1.0 - (i.toDouble / layers))
    val r = (baseColor.getRed + (lightAccent.getRed - baseColor.getRed) * (1 - factor)).toInt
    val g = (baseColor.getGreen + (lightAccent.getGreen - baseColor.getGreen) * (1 - factor)).toInt
    val b = (baseColor.getBlue + (lightAccent.getBlue - baseColor.getBlue) * (1 - factor)).toInt
    canvas.fill(math.max(0, math.min(255, r)), math.max(0, math.min(255, g)), math.max(0, math.min(255, b)))
    canvas.noStroke()
    val offset = (1 - factor) * (safeSize * 0.28)
    val curDim = math.max(0.1, safeSize * factor)
    canvas.ellipse(cx - offset, cy + offset, curDim, curDim)
  }
  canvas.fill(255, 255, 255, 45)
  val spotX = cx + math.cos(rotAngle) * (safeSize * 0.25)
  val spotY = cy + math.sin(rotAngle) * (safeSize * 0.15)
  canvas.ellipse(spotX, spotY, math.max(0.1, safeSize * 0.35), math.max(0.1, safeSize * 0.2))
}

def project3D(x3d: Double, y3d: Double, z3d: Double, cx: Double, cy: Double): (Double, Double) = {
  val cosY = math.cos(rotAngleY); val sinY = math.sin(rotAngleY)
  val cosX = math.cos(rotAngleX); val sinX = math.sin(rotAngleX)
  val x1 = x3d * cosY + z3d * sinY
  val z1 = -x3d * sinY + z3d * cosY
  val y2 = y3d * cosX - z1 * sinX
  (cx + x1, cy + y2)
}

def drawFullSolarSystem(canvas: CanvasDraw): Unit = {
  val (centerX, centerY) = (cwidth / 2.0 - 20.0, cheight / 2.0 - 10.0)
  val pList = getActivePlanetList()
  canvas.fill(0, 255, 255, 180); canvas.text("?? Drag mouse to rotate Solar System in 3D!", centerX - 120, cheight - 25)

  for (i <- pList.indices) {
    val radius = i * 28.0 + 50.0
    canvas.noFill(); canvas.stroke(0, 255, 255, 45); canvas.strokeWeight(1.0)
    canvas.ellipse(centerX, centerY, radius * 2.0, radius * 2.0 * 0.6)
  }

  val sunGlow = math.max(10.0, (math.sin(globalTime * 6) * 10 + 72).toDouble)
  draw3DSphere(canvas, centerX, centerY, sunGlow, cm.rgb(255, 110, 0), cm.rgb(255, 255, 190), globalTime * 2)

  val planetSpeeds = Array(0.7, 0.55, 0.42, 0.35, 0.28, 0.22, 0.17, 0.12)
  val activeMap = getActivePlanetDetailsMap()

  for (i <- pList.indices) {
    val planetName = pList(i); val radius = i * 28.0 + 50.0
    val angle = globalTime * (if(i < planetSpeeds.length) planetSpeeds(i) else 0.2)
    val x3d = radius * math.cos(angle); val z3d = radius * math.sin(angle)
    val (curX, curY) = project3D(x3d, 0.0, z3d, centerX, centerY)

    var (pCol, pAccent, pRing) = (cm.rgb(200, 200, 200), cm.rgb(255, 255, 255), false)
    activeMap.foreach { pd => if (pd.name == planetName) { pCol = pd.surfaceCol; pAccent = pd.accent3D; pRing = pd.hasRings } }
    val pSize = math.max(1.0, if (Array("Earth", "Kepler-22b", "Kepler-452b").contains(planetName)) 18.0 else (12.0 + (i * 0.4)))
    
    if (pRing) { canvas.noFill(); canvas.stroke(245, 220, 180, 210); canvas.strokeWeight(3.2); canvas.ellipse(curX, curY, pSize * (2.2 + math.sin(globalTime * 2) * 0.1), math.max(0.1, pSize * 0.8 * math.abs(math.cos(rotAngleX)))) }
    draw3DSphere(canvas, curX, curY, pSize, pCol, pAccent, globalTime * 3 + i)

    if (!planetLocked && isMousePressed && math.abs(mouseX - curX) < 20 && math.abs(mouseY - curY) < 20) {
      if (planetName == "Earth") { selectedPlanet = "Earth"; customAlertMessage = "You are already on Earth!"; alertTimer = 3.0 } else selectedPlanet = planetName
    }

    if (planetName == selectedPlanet) { canvas.noFill(); canvas.stroke(0, 255, 255); canvas.strokeWeight(math.max(1.0, 3.0 + math.sin(neonGlowPhase * 6).toFloat * 1.5)); canvas.ellipse(curX, curY, pSize + 16, pSize + 16) }
  }
}

def drawPlanetList(canvas: CanvasDraw): Unit = {
  canvas.fill(15, 20, 45, 245); canvas.stroke(0, 255, 255); canvas.strokeWeight(2.5); canvas.rect(10, 10, 230, 380)
  canvas.fill(255, 255, 255); canvas.text("Select Target Planet", 15, 30)
  canvas.fill(0, math.max(0, math.min(255, (math.sin(globalTime * 4) * 30 + 180).toInt)), 220); canvas.stroke(255, 255, 255); canvas.strokeWeight(1.5); canvas.rect(15, 40, 220, 28)
  canvas.fill(255, 255, 255); canvas.text(if (currentSolarSystemId == 1) " System: Real Solar" else " System: Kepler Planet", 18, 59)
  
  if (isMousePressed && mouseX >= 15 && mouseX <= 235 && mouseY >= 40 && mouseY <= 68) {
    currentSolarSystemId = if (currentSolarSystemId == 1) 2 else 1
    selectedPlanet = if (currentSolarSystemId == 1) "Mars" else "Kepler-22b"
    customAlertMessage = if (currentSolarSystemId == 1) "Real Solar System Selected!" else "Kepler Exoplanet System Selected!"
    alertTimer = 2.5
  }
  val activeMap = getActivePlanetDetailsMap(); val pList = getActivePlanetList()
  for (i <- pList.indices) {
    val py = 88.0 + (i * 32.0); var rName = pList(i)
    activeMap.foreach { pd => if (pd.name == pList(i)) rName = pd.realName }
    if (!planetLocked && isMousePressed && mouseX >= 15 && mouseX <= 235 && mouseY >= py - 12 && mouseY <= py + 12) {
      if (pList(i) == "Earth") { selectedPlanet = "Earth"; customAlertMessage = "You are already on Earth!"; alertTimer = 2.5 } else selectedPlanet = pList(i)
    }
    if (pList(i) == selectedPlanet) { canvas.fill(0, 180, 80); canvas.rect(15, py - 12, 220, 26); canvas.fill(255, 255, 255) } else canvas.fill(210, 210, 210)
    canvas.text(s" $rName", 18, py)
  }
}

def draw3DPlanetPreview(canvas: CanvasDraw): Unit = {
  val (boxX, boxY, boxW, boxH) = (cwidth - 250.0, 15.0, 235.0, 225.0)
  canvas.fill(15, 20, 45, 245); canvas.stroke(0, 255, 255); canvas.strokeWeight(2.0); canvas.rect(boxX, boxY, boxW, boxH)
  canvas.fill(255, 255, 255); canvas.text(s"3D Hologram: $selectedPlanet", boxX + 12, boxY + 22)
  val (cx, cy) = (boxX + boxW / 2.0, boxY + boxH / 2.0 + 15.0)
  var (pCol, pAccent, pRings) = (cm.rgb(200, 70, 40), cm.rgb(255, 130, 100), false)
  getActivePlanetDetailsMap().foreach { pd => if (pd.name == selectedPlanet) { pCol = pd.surfaceCol; pAccent = pd.accent3D; pRings = pd.hasRings } }
  if (pRings) { canvas.noFill(); canvas.stroke(220, 210, 180, 230); canvas.strokeWeight(8.5); canvas.ellipse(cx, cy, 185, 54) }
  draw3DSphere(canvas, cx, cy, 115, pCol, pAccent, globalTime * 2.5)
}

def drawPlanetSurfaceBackground(canvas: CanvasDraw): Unit = {
  var (surfCol, darkSurfCol) = (cm.rgb(100, 30, 15), cm.rgb(50, 10, 5))
  getActivePlanetDetailsMap().foreach { pd => if (pd.name == selectedPlanet) { surfCol = pd.surfaceCol; darkSurfCol = pd.darkSurfCol } }
  
  canvas.fill(surfCol); canvas.noStroke(); canvas.rect(0, 0, cwidth, 140)
  canvas.fill(darkSurfCol);
  canvas.ellipse(120, 40, 60, 20); canvas.ellipse(420, 60, 90, 25); canvas.ellipse(650, 45, 70, 22); canvas.rect(0, 120, cwidth, 25)
  
  if (isFlagPlantedOnPlanet) drawWavingTricolorFlagWithAshokChakra(canvas, plantedFlagX, plantedFlagY)
}

def drawWavingTricolorFlagWithAshokChakra(canvas: CanvasDraw, fx: Double, fy: Double): Unit = {
  val (fw, fh) = (52.0, 10.0); canvas.stroke(230, 230, 230); canvas.strokeWeight(3.5); canvas.line(fx, fy + 45, fx, fy - 12)
  var x = 0.0; while (x < fw) {
    val yOffset = math.sin(waveAngle + (x * 0.12)) * 4.5; canvas.strokeWeight(2.8)
    canvas.stroke(255, 153, 51); canvas.line(fx + x, fy + fh * 2 + yOffset, fx + x, fy + fh * 3 + yOffset)
    canvas.stroke(255, 255, 255); canvas.line(fx + x, fy + fh + yOffset, fx + x, fy + fh * 2 + yOffset)
    canvas.stroke(18, 136, 7); canvas.line(fx + x, fy + yOffset, fx + x, fy + fh + yOffset); x += 2.0
  }
  val (chakraX, chakraY) = (fx + 26.0, fy + (fh * 1.5) + math.sin(waveAngle + (26.0 * 0.12)) * 4.5)
  canvas.fill(0, 0, 190); canvas.noStroke(); canvas.ellipse(chakraX, chakraY, 8.5, 8.5)
}

def drawPersonCharacter(canvas: CanvasDraw, px: Double, py: Double, isArmy: Boolean, isSaluting: Boolean): Unit = {
  canvas.pushMatrix(); canvas.translate(px, py)
  canvas.fill(0, 0, 0, 90); canvas.noStroke(); canvas.ellipse(0, -25, 24, 8)
  val stepSwing = if (startAnthemPhase) 0.0 else math.sin(globalTime * 10) * 5.5
  canvas.stroke(if (isArmy) cm.rgb(35, 65, 22) else cm.rgb(190, 190, 210)); canvas.strokeWeight(5.5)
  canvas.line(-5, 0, -6 + stepSwing, -22); canvas.line(5, 0, 6 - stepSwing, -22)
  canvas.fill(15, 15, 15); canvas.noStroke(); canvas.rect(-9, -25, 7, 4); canvas.rect(3, -25, 7, 4)
  canvas.fill(if (isArmy) cm.rgb(80, 110, 50) else cm.rgb(245, 245, 255)); canvas.stroke(0, 255, 255); canvas.strokeWeight(1.5); canvas.rect(-10, 0, 20, 32)
  canvas.fill(if (isArmy) cm.rgb(215, 165, 125) else cm.rgb(255, 255, 255)); canvas.ellipse(0, 42, 18, 18)
  if (isArmy) { canvas.fill(50, 80, 30); canvas.rect(-10, 48, 20, 6) } else { canvas.fill(0, 190, 250); canvas.ellipse(0, 42, 10, 8) }
  canvas.stroke(if (isArmy) cm.rgb(80, 110, 50) else cm.rgb(230, 230, 240)); canvas.strokeWeight(4.5)
  if (isSaluting) { canvas.line(-8, 22, -12, 4); canvas.line(8, 22, 14, 34); canvas.line(14, 34, 8, 44) } else { canvas.line(-8, 22, -12, 4); canvas.line(8, 22, 12, 4) }
  canvas.popMatrix()
}

def drawAstronautPlayer(canvas: CanvasDraw, px: Double, py: Double): Unit = {
  canvas.pushMatrix()
  canvas.translate(px, py)
  canvas.scale(1.0, -1.0)
  
  val floatAnim = math.sin(globalTime * 8) * 4.5
  canvas.translate(0, floatAnim)

  if (selectedRobotType == 1) {
    canvas.fill(0, 0, 0, 80); canvas.noStroke(); canvas.ellipse(0, 32, 28, 7)
    canvas.stroke(0, 255, 255); canvas.strokeWeight(5.5)
    canvas.line(-7, 0, -9, 20); canvas.line(7, 0, 9, 20)
    canvas.fill(15, 25, 45); canvas.stroke(0, 255, 255); canvas.strokeWeight(2.5)
    canvas.rect(-14, -16, 28, 28)
    canvas.fill(255, 215, 0); canvas.rect(-18, -4, 6, 12); canvas.rect(12, -4, 6, 12)
    val coreGlow = (180 + math.sin(globalTime * 12) * 75).toInt
    canvas.fill(0, coreGlow, 255); canvas.ellipse(0, -2, 10, 10) 
    canvas.fill(20, 35, 65); canvas.stroke(0, 255, 255); canvas.strokeWeight(2.0); canvas.rect(-11, -32, 22, 14)
    canvas.fill(0, 255, 180); canvas.rect(-8, -28, 16, 5) 
    canvas.fill(255, 215, 0); canvas.ellipse(0, -35, 4, 4) 
  } 
  else if (selectedRobotType == 2) {
    canvas.fill(0, 0, 0, 100); canvas.noStroke(); canvas.ellipse(0, 34, 34, 9)
    canvas.stroke(255, 140, 0); canvas.strokeWeight(7.5)
    canvas.line(-9, 0, -11, 20); canvas.line(9, 0, 11, 20)
    canvas.fill(45, 20, 20); canvas.stroke(255, 60, 0); canvas.strokeWeight(3.0)
    canvas.rect(-18, -18, 36, 30)
    canvas.fill(255, 30, 30); canvas.rect(-14, -8, 28, 5)
    canvas.fill(80, 80, 90); canvas.rect(-22, -12, 6, 16); canvas.rect(16, -12, 6, 16)
    canvas.fill(60, 30, 30); canvas.rect(-13, -34, 26, 14)
    canvas.fill(255, 220, 0); canvas.ellipse(-5, -28, 5, 5); canvas.ellipse(5, -28, 5, 5) 
  }
  else {
    canvas.fill(0, 0, 0, 80); canvas.noStroke(); canvas.ellipse(0, 32, 26, 6)
    canvas.fill(80, 40, 120); canvas.stroke(200, 100, 255); canvas.strokeWeight(2.0)
    canvas.ellipse(-10, 15, 10, 10); canvas.ellipse(10, 15, 10, 10)
    canvas.fill(160, 40, 220); canvas.stroke(255, 150, 255); canvas.strokeWeight(2.0)
    canvas.ellipse(0, -8, 28, 28)
    val alienEyeGlow = (120 + math.sin(globalTime * 10) * 125).toInt
    canvas.fill(0, alienEyeGlow, 255); canvas.ellipse(0, -8, 12, 12) 
    canvas.stroke(255, 255, 255); canvas.strokeWeight(2.5)
    canvas.line(-8, 8, -12, 16); canvas.line(8, 8, 12, 16)
    canvas.stroke(0, 255, 255); canvas.line(0, -22, 0, -34); canvas.fill(0, 255, 255); canvas.ellipse(0, -34, 5, 5) 
  }

  canvas.popMatrix()
}

def drawRobotChallengeGameScreen(canvas: CanvasDraw): Unit = {
  canvas.fill(5, 5, 20); canvas.noStroke(); canvas.rect(0, 0, cwidth, cheight)
  for (i <- 0 until 180) { canvas.stroke(255, 255, 255, 180); canvas.strokeWeight(starSize(i)); canvas.point(starX(i), starY(i)) }

  canvas.fill(255, 215, 0); canvas.stroke(0, 255, 255); canvas.strokeWeight(3.0)
  canvas.text("? ROBOT ACCESS CHALLENGE ?", cwidth / 2.0 - 140, cheight - 35)
  canvas.fill(200, 240, 255); canvas.noStroke()
  canvas.text(s"Catch $robotChallengeTarget Cyber Orbs using Arrow Keys [LEFT/RIGHT] to Unlock Mission Access!", cwidth / 2.0 - 230, cheight - 65)

  canvas.fill(0, 255, 150); canvas.text(s"Orbs Collected: $robotChallengeScore / $robotChallengeTarget", cwidth / 2.0 - 80, cheight - 95)

  val orbPulse = math.max(10.0, 15.0 + math.sin(globalTime * 10) * 5.0)
  draw3DSphere(canvas, cyberEnergyX, cyberEnergyY, orbPulse, cm.rgb(0, 255, 255), cm.rgb(255, 255, 255), globalTime * 5)

  drawAstronautPlayer(canvas, robotGamePlayerX, robotGamePlayerY)
}

def drawRobotSelectionScreen(canvas: CanvasDraw): Unit = {
  canvas.fill(5, 8, 22); canvas.noStroke(); canvas.rect(0, 0, cwidth, cheight)
  
  canvas.stroke(0, 255, 255, 30); canvas.strokeWeight(1.0)
  for (gx <- 0 until cwidth.toInt by 40) canvas.line(gx, 0, gx, cheight)
  for (gy <- 0 until cheight.toInt by 40) canvas.line(0, gy, cwidth, gy)

  for (i <- 0 until 180) { canvas.stroke(255, 255, 255, 180); canvas.strokeWeight(starSize(i)); canvas.point(starX(i), starY(i)) }

  val titleGlow = math.max(1.0, math.sin(globalTime * 6) * 2.5 + 4.0)
  canvas.fill(255, 215, 0); canvas.stroke(255, 153, 51); canvas.strokeWeight(titleGlow)
  canvas.text("? CHOOSE & UNLOCK YOUR ISRO ROBOT AVATAR ?", cwidth / 2.0 - 200, cheight - 35)
  canvas.fill(200, 230, 255); canvas.noStroke()
  canvas.text("Select your favourite Robot and complete the challenge to start your mission!", cwidth / 2.0 - 220, cheight - 65)

  val rBoxW = 150.0; val rBoxH = 185.0; val startX = cwidth / 2.0 - 240.0; val boxY = cheight / 2.0 - 85.0

  val robotNames = Array("1. NEON TITAN", "2. MECHA WARRIOR", "3. ALIEN ROVER")
  val robotDescs = Array("Speed & Cyber Core", "Heavy Armor Plated", "Alien Tech & Wheels")

  for (i <- 0 until 3) {
    val curX = startX + (i * 170.0)
    val isSelected = (selectedRobotType == i + 1)
    
    canvas.fill(12, 22, 45, 245)
    if (isSelected) { canvas.stroke(0, 255, 255); canvas.strokeWeight(3.8) } 
    else { canvas.stroke(80, 90, 130); canvas.strokeWeight(1.5) }
    canvas.rect(curX, boxY, rBoxW, rBoxH)

    canvas.fill(255, 255, 255)
    canvas.text(robotNames(i), curX + 12, boxY + rBoxH - 20)
    canvas.fill(0, 255, 200)
    canvas.text(robotDescs(i), curX + 10, boxY + rBoxH - 38)

    val tempRob = selectedRobotType
    selectedRobotType = i + 1
    drawAstronautPlayer(canvas, curX + rBoxW / 2.0, boxY + 68.0)
    selectedRobotType = tempRob

    if (isMousePressed && mouseX >= curX && mouseX <= curX + rBoxW && mouseY >= boxY && mouseY <= boxY + rBoxH) {
      selectedRobotType = i + 1
      playSoundFx(75, 150)
    }
  }

  val btnX = cwidth / 2.0 - 130.0; val btnY = 30.0
  if (!isRobotUnlocked) {
    canvas.fill(255, 90, 0); canvas.stroke(255, 230, 0); canvas.strokeWeight(2.5); canvas.rect(btnX, btnY, 260, 42)
    canvas.fill(255, 255, 255); canvas.text("UNLOCK & PLAY CHALLENGE ?", btnX + 18, btnY + 26)

    if (isMousePressed && mouseX >= btnX && mouseX <= btnX + 260 && mouseY >= btnY && mouseY <= btnY + 42) {
      robotChallengeScore = 0
      currentStoryStage = -3 
      playSoundFx(80, 250)
    }
  } else {
    canvas.fill(18, 136, 7); canvas.stroke(255, 255, 255); canvas.strokeWeight(2.5); canvas.rect(btnX, btnY, 260, 42)
    canvas.fill(255, 255, 255); canvas.text("CONFIRM ROBOT ?", btnX + 60, btnY + 26)

    if (isMousePressed && mouseX >= btnX && mouseX <= btnX + 260 && mouseY >= btnY && mouseY <= btnY + 42) {
      currentStoryStage = -1 
      playSoundFx(80, 250)
    }
  }
}

def drawParadeGroundScene(canvas: CanvasDraw): Unit = {
  canvas.fill(34, 139, 34); canvas.noStroke(); canvas.rect(0, 0, cwidth, 140); canvas.fill(25, 95, 25); canvas.rect(0, 125, cwidth, 25)
  val (fx, fy, fw, fh) = (cwidth / 2.0 - 70.0, cheight / 2.0 + 10.0, 140.0, 26.0); canvas.stroke(230, 230, 230); canvas.strokeWeight(6.5); canvas.line(fx, fy + fh * 3 + 20, fx, fy - 140)
  var x = 0.0; while (x < fw) {
    val yOffset = math.sin(waveAngle + (x * 0.05)) * 8.5; canvas.strokeWeight(5.2)
    canvas.stroke(255, 153, 51); canvas.line(fx + x, fy + fh * 2 + yOffset, fx + x, fy + fh * 3 + yOffset)
    canvas.stroke(255, 255, 255); canvas.line(fx + x, fy + fh + yOffset, fx + x, fy + fh * 2 + yOffset)
    canvas.stroke(18, 136, 7); canvas.line(fx + x, fy + yOffset, fx + x, fy + fh + yOffset); x += 4.0
  }
  canvas.fill(0, 0, 190); canvas.noStroke(); canvas.ellipse(fx + 70.0, fy + (fh * 1.5) + math.sin(waveAngle + 3.5) * 8.5, 19, 19)
  
  petalShower.foreach { p => canvas.fill(p.col.getRed, p.col.getGreen, p.col.getBlue, 200); canvas.ellipse(p.x, p.y, p.size, p.size * 0.6) }
  for (i <- 0 until 3) { drawPersonCharacter(canvas, astroParadeWalkX - (i * 35.0), 165.0, false, isParadeInPosition); drawPersonCharacter(canvas, armyParadeWalkX + (i * 35.0), 165.0, true, isParadeInPosition) }
  drawPatrioticFullText(canvas)
}

def drawEarthFirstLandingScene(canvas: CanvasDraw): Unit = {
  canvas.fill(34, 139, 34); canvas.noStroke(); canvas.rect(0, 0, cwidth, 140); canvas.fill(30, 110, 210); canvas.rect(0, 125, cwidth, 25); drawISRORocket(canvas)
  if (isLandedOnEarth) drawPersonCharacter(canvas, rocketCurrentX + earthAstronautWalkRightProgress, 165.0, false, false)
}

def drawLaunchAndUI(canvas: CanvasDraw): Unit = {
  if (!isSpaceTravelling && !isLaunched) {
    val (btnX, btnY) = (cwidth - 250.0, 250.0)
    if (!isTimerRunning && !quizActive) {
      canvas.fill(255, 90, 0); canvas.stroke(255, 230, 0); canvas.strokeWeight(math.max(1.0, math.sin(neonGlowPhase * 5) * 2.5 + 4.5).toFloat); canvas.rect(btnX, btnY, 235, 45)
      canvas.fill(255, 255, 255); canvas.text("? Launch ISRO Mission", btnX + 22, btnY + 28)
      if (isMousePressed && mouseX >= btnX && mouseX <= btnX + 235 && mouseY >= btnY && mouseY <= btnY + 45) { planetLocked = true; isTimerRunning = true; countdownTimer = 10.0 }
    }
    if (isTimerRunning) { canvas.fill(255, 215, 0); canvas.text(s"Launch Countdown: ${countdownTimer.toInt}s", btnX + 15, btnY + 28) }
  }
  if (readyToReturnEarth && !isReturnLaunched && !isAstronautReturningIn && !isLandingOnEarth) {
    canvas.fill(18, 136, 7); canvas.stroke(0, 255, 160); canvas.strokeWeight(math.max(1.0, math.sin(neonGlowPhase * 5) * 2.5 + 5.0).toFloat); canvas.rect(cwidth / 2.0 - 120, 25, 240, 45)
    canvas.fill(255, 255, 255); canvas.text("? Return to Earth", cwidth / 2.0 - 85, 52)
    if (isMousePressed && mouseX >= cwidth / 2.0 - 120 && mouseX <= cwidth / 2.0 + 120 && mouseY >= 25 && mouseY <= 70) isAstronautReturningIn = true
  }
  if (isReturnTimerRunning) { canvas.fill(255, 215, 0); canvas.text(s"Return Time: ${returnCountdownTimer.toInt}s", cwidth / 2.0 - 110, 80) }
}

def drawISRORocket(canvas: CanvasDraw): Unit = {
  canvas.pushMatrix(); canvas.translate(rocketCurrentX, rocketCurrentY)
  
  if (isLanded) canvas.fill(0, 0, 0, 100); canvas.noStroke(); canvas.ellipse(0, -45, 50, 12)

  if (isSpaceTravelling || isLandingOnPlanet || isReturnLaunched || isLandingOnEarth) {
    val flameSize = math.max(1.0, randomDouble(25.0, 42.0))
    canvas.fill(255, 80, 0, 200); canvas.noStroke(); canvas.ellipse(0, -58, 22, flameSize)
    canvas.fill(255, 180, 0, 230); canvas.ellipse(0, -54, 14, flameSize * 0.7)
    canvas.fill(255, 255, 200, 255); canvas.ellipse(0, -50, 6, flameSize * 0.4)
  }

  if (!isSpaceTravelling && !isLaunched) { canvas.fill(255, 255, 255); canvas.text("ISRO", -18, 48) }
  
  canvas.fill(245, 245, 255); canvas.stroke(0, 255, 255); canvas.strokeWeight(2.8); canvas.rect(-12, -45, 24, 45)
  canvas.fill(200, 200, 215); canvas.noStroke(); canvas.rect(0, -45, 12, 45)
  canvas.fill(255, 255, 255, math.max(0, math.min(255, (math.sin(globalTime * 8) * 40 + 60).toInt))); canvas.rect(-10, -42, 4, 40)
  canvas.fill(255, 65, 65); canvas.stroke(255, 210, 210); canvas.strokeWeight(2.0); canvas.beginShape(); canvas.vertex(0, 25); canvas.vertex(-12, 0); canvas.vertex(12, 0); canvas.endShape()
  canvas.fill(225, 225, 235); canvas.stroke(100, 100, 130); canvas.rect(-20, -35, 7, 35); canvas.rect(13, -35, 7, 35)

  canvas.stroke(210, 210, 210); canvas.strokeWeight(3.8)
  canvas.line(-12, -40, -12 - (landingLegProgress * 15), -45 - (landingLegProgress * 15))
  canvas.line(-12 - (landingLegProgress * 15), -45 - (landingLegProgress * 15), -12 - (landingLegProgress * 22), -45 - (landingLegProgress * 15))
  canvas.line(12, -40, 12 + (landingLegProgress * 15), -45 - (landingLegProgress * 15))
  canvas.line(12 + (landingLegProgress * 15), -45 - (landingLegProgress * 15), 12 + (landingLegProgress * 22), -45 - (landingLegProgress * 15))

  if (isLanded && !isParadeSceneActive) {
    canvas.fill(40, 40, 55); canvas.rect(-8, -30, 16, math.max(0.1, 20.0 * doorOpeningProgress))
    
    // 1. Robot First Exit & Return to Spaceship Sequence
    if (!robotInsideDone) {
      canvas.pushMatrix(); canvas.translate(-robotWalkOutDist, -10.0)
      drawAstronautPlayer(canvas, 0, 0)
      canvas.popMatrix()
      canvas.fill(0, 255, 255); canvas.text("? Robot deploying & returning to spaceship...", -190, 85)
    } 
    else {
      // 2. Astronaut Exit Sequence
      if (flagPlantProgress > 0.0 && !isFlagPlantedOnPlanet) drawWavingTricolorFlagWithAshokChakra(canvas, -80.0, -20.0)
      
      if (astronautOut && !isLandedOnEarth) {
        canvas.pushMatrix(); canvas.translate(-astroWalkToPlantX, -10.0)
        drawAstronautPlayer(canvas, 0, 0)
        canvas.popMatrix()

        if (!isFlagPlantedOnPlanet) {
          if (astroWaitTimer < 2.5) {
            canvas.fill(0, 255, 255); canvas.text("? Checking the atmosphere & gravity...", -160, 95)
          } else {
            canvas.fill(255, 215, 0); canvas.text("?? Use LEFT / RIGHT Arrow Keys to Walk & Plant Flag!", -170, 75)
          }
        }

        if (astroWaitTimer >= 2.5 && astroWalkToPlantX >= 70.0 && flagPlantProgress == 0.0) {
          val (flagBtnX, flagBtnY) = (-50.0, 25.0)
          canvas.fill(255, 153, 51); canvas.stroke(255, 255, 255); canvas.strokeWeight(2.0)
          canvas.rect(flagBtnX, flagBtnY, 110, 32)
          canvas.fill(255, 255, 255); canvas.text("?? PLANT FLAG", flagBtnX + 10, flagBtnY + 20)

          if (isMousePressed && mouseX >= (rocketCurrentX + flagBtnX) && mouseX <= (rocketCurrentX + flagBtnX + 110) &&
              mouseY >= (rocketCurrentY + flagBtnY) && mouseY <= (rocketCurrentY + flagBtnY + 32)) {
            flagPlantProgress = 0.01
            playSoundFx(78, 300)
          }
        }
      }
    }
  }
  canvas.popMatrix()
}

def drawQuizPopup(canvas: CanvasDraw): Unit = {
  if (quizActive) {
    val (qx, qy) = (cwidth / 2.0 - 225.0, cheight / 2.0 - 130.0)
    canvas.fill(15, 20, 45, 250); canvas.stroke(255, 215, 0); canvas.strokeWeight(3.5); canvas.rect(qx, qy, 450, 260)
    canvas.fill(255, 255, 255); canvas.text(s"ISRO Quiz: $selectedPlanet", qx + 20, qy + 25)
    var qText = ""; var opts = Array("A", "B", "C", "D"); var corr = 0
    getActivePlanetDetailsMap().foreach { pd => if (pd.name == selectedPlanet) { qText = pd.quizQuestion; opts = pd.quizOptions; corr = pd.correctOpt } }
    canvas.fill(255, 225, 110); canvas.text(qText, qx + 15, qy + 52)
    for (i <- opts.indices) {
      val optY = qy + 75.0 + (i * 32.0); canvas.fill(30, 45, 85); canvas.stroke(0, 255, 255); canvas.strokeWeight(1.5); canvas.rect(qx + 15, optY, 420, 26)
      canvas.fill(255, 255, 255); canvas.text(opts(i), qx + 25, optY + 18)
      if (isMousePressed && mouseX >= qx + 15 && mouseX <= qx + 435 && mouseY >= optY && mouseY <= optY + 26) {
        if (i == corr) { quizActive = false; isSpaceTravelling = true; spaceTravelProgress = 0.0; customAlertMessage = "Correct Answer! Mission Continues!"; alertTimer = 3.0 }
        else quizFeedback = "Incorrect Answer! Try Again."
      }
    }
    if (quizFeedback != "") { canvas.fill(255, 60, 60); canvas.text(quizFeedback, qx + 20, qy + 235) }
  }
}

def drawPlanetInfoPopup(canvas: CanvasDraw): Unit = {
  if (infoPanelShown) {
    val (ix, iy) = (cwidth / 2.0 - 230.0, cheight / 2.0 - 130.0)
    canvas.fill(15, 20, 45, 250); canvas.stroke(0, 255, 255); canvas.strokeWeight(3.5); canvas.rect(ix, iy, 460, 260)
    val signalGlow = math.max(0, math.min(255, (math.sin(globalTime * 10) * 100 + 155).toInt))
    canvas.fill(0, signalGlow, 255); canvas.noStroke(); canvas.ellipse(ix + 430, iy + 25, 10, 10)

    var rName = selectedPlanet; val activeMap = getActivePlanetDetailsMap()
    activeMap.foreach { pd => if (pd.name == selectedPlanet) rName = pd.realName }
    canvas.fill(255, 215, 0); canvas.text(s" Mission Info: $rName (Page $infoPageNumber/3)", ix + 20, iy + 25)

    var (l1, l2, l3, l4, l5, l6, l7, l8) = ("", "", "", "", "", "", "", "")
    activeMap.foreach { pd => if (pd.name == selectedPlanet) { l1 = pd.infoLine1; l2 = pd.infoLine2; l3 = pd.infoLine3; l4 = pd.infoLine4; l5 = pd.infoLine5; l6 = pd.infoLine6; l7 = pd.infoLine7; l8 = pd.infoLine8 } }

    if (infoPageNumber == 1) { canvas.fill(100, 225, 255); canvas.text(" Primary Planetary Characteristics:", ix + 20, iy + 52); canvas.fill(225, 225, 245); canvas.text(l1, ix + 20, iy + 80); canvas.text(l2, ix + 20, iy + 110); canvas.text(l3, ix + 20, iy + 140) }
    else if (infoPageNumber == 2) { canvas.fill(100, 255, 210); canvas.text(" Real Scientific & Distance Data:", ix + 20, iy + 52); canvas.fill(225, 225, 245); canvas.text(l4, ix + 20, iy + 80); canvas.text(l5, ix + 20, iy + 110); canvas.text(l6, ix + 20, iy + 140) }
    else { canvas.fill(255, 190, 110); canvas.text(" Atmospheric Spectrum & ISRO Notes:", ix + 20, iy + 52); canvas.fill(225, 225, 245); canvas.text(l7, ix + 20, iy + 80); canvas.text(l8, ix + 20, iy + 110) }

    canvas.fill(45, 110, 210); canvas.rect(ix + 20, iy + 195, 100, 35); canvas.fill(255, 255, 255); canvas.text("Next Page", ix + 30, iy + 218)
    if (isMousePressed && mouseX >= ix + 20 && mouseX <= ix + 120 && mouseY >= iy + 195 && mouseY <= iy + 230) infoPageNumber = if (infoPageNumber == 3) 1 else infoPageNumber + 1

    canvas.fill(0, 190, 90); canvas.rect(ix + 140, iy + 195, 150, 35); canvas.fill(255, 255, 255); canvas.text(" Play Game", ix + 170, iy + 218)
    if (isMousePressed && mouseX >= ix + 140 && mouseX <= ix + 290 && mouseY >= ix + 195 && mouseY <= iy + 230) { infoPanelShown = false; isMiniGameActive = true; dieAndResetDevilLevel("Game Started!") }

    canvas.fill(190, 45, 45); canvas.rect(ix + 300, iy + 195, 130, 35); canvas.fill(255, 255, 255); canvas.text("Close X", ix + 325, iy + 218)
    if (isMousePressed && mouseX >= ix + 300 && mouseX <= ix + 430 && mouseY >= ix + 195 && mouseY <= iy + 230) { infoPanelShown = false; readyToReturnEarth = true }
  }
}

def drawCustomAlertPopup(canvas: CanvasDraw): Unit = {
  if (alertTimer > 0 && customAlertMessage != "") {
    canvas.fill(230, 35, 35, 240); canvas.stroke(255, 255, 255); canvas.strokeWeight(2.2); canvas.rect(cwidth / 2.0 - 210.0, 80.0, 420, 50)
    canvas.fill(255, 255, 255); canvas.text(customAlertMessage, cwidth / 2.0 - 195.0, 110.0)
  }
}

def drawMiniGames(canvas: CanvasDraw): Unit = {
  if (isMiniGameActive) {
    val (gx, gy) = (cwidth / 2.0 - 220.0, cheight / 2.0 - 100.0)
    canvas.fill(10, 10, 25, 250); canvas.stroke(0, 255, 210); canvas.strokeWeight(3.5); canvas.rect(gx, gy, 440, 210)
    canvas.fill(255, 65, 65); canvas.text(s" Time: ${miniGameTimer.toInt}s", gx + 290, gy + 190)

    if (activeGameMode == 1) {
      canvas.fill(255, 255, 255); canvas.text(s" Asteroid Dodger | Score: $dodgerScore/80", gx + 15, gy + 190)
      canvas.fill(0, 255, 210); canvas.stroke(255, 255, 255); canvas.strokeWeight(1.5)
      canvas.beginShape(); canvas.vertex(dodgerShipX, dodgerShipY + 12); canvas.vertex(dodgerShipX - 12, dodgerShipY - 12); canvas.vertex(dodgerShipX + 12, dodgerShipY - 12); canvas.endShape()
      for (i <- 0 until dodgerCount) draw3DSphere(canvas, dodgerAstX(i), dodgerAstY(i), 22, cm.rgb(190, 110, 70), cm.rgb(255, 190, 110), globalTime * 2)
    } else if (activeGameMode == 3) {
      val (padX, padY, padW) = (gx + 180.0, gy + 20.0, 80.0)
      canvas.fill(255, 255, 255); canvas.text(s" Lander Fuel: ${landerFuel.toInt}%", gx + 15, gy + 190)
      canvas.fill(0, 255, 110); canvas.rect(padX, padY, padW, 8); canvas.fill(255, 255, 255); canvas.text("TARGET PAD", padX + 5, padY + 18)
      canvas.fill(255, 210, 0); canvas.stroke(255, 255, 255); canvas.strokeWeight(1.5); canvas.rect(landerX - 12, landerY - 12, 24, 24)
      if (isKeyPressed(Kc.VK_UP) && landerFuel > 0) { canvas.fill(255, 110, 0); canvas.noStroke(); canvas.ellipse(landerX, landerY - 20, 10, 16) }
    } else {
      canvas.fill(255, 255, 255); canvas.text(s" Rover Adventure Game", gx + 15, gy + 190)
      canvas.fill(255, 153, 51); canvas.rect(gx + 15, gy + 170, 410, 12)
      if (!isDevilKeyCollected) { canvas.fill(255, 215, 0); canvas.ellipse(trollKeyX, trollKeyY, 16, 16) }
      canvas.fill(if (isDevilKeyCollected) cm.rgb(0, 255, 160) else cm.rgb(110, 110, 110)); canvas.rect(exitDoorX, gy + 135, 30, 40)
      drawAstronautPlayer(canvas, devilPlayerX, devilPlayerY)
    }
  }
}

def drawPatrioticFullText(canvas: CanvasDraw): Unit = {
  if (charIndex > 0) {
    val lines = fullAnthemText.substring(0, charIndex).split("\n"); var startY = cheight - 40.0
    for (i <- lines.indices) {
      canvas.fill(255, 215, 0, 110); canvas.text(lines(i), 21.0, startY - 1)
      canvas.fill(255, 255, 255); canvas.text(lines(i), 20.0, startY); startY -= 18.0
    }
  }
}

def drawCleanEndScreen(canvas: CanvasDraw): Unit = {
  canvas.fill(0, 0, 0); canvas.noStroke(); canvas.rect(0, 0, cwidth, cheight)
  val (centerX, centerY) = (cwidth / 2.0, cheight / 2.0); val glowFactor = math.max(1.0, (math.sin(globalTime * 5) * 2 + 4.0).toFloat)
  
  canvas.fill(255, 215, 0); canvas.stroke(255, 160, 0); canvas.strokeWeight(glowFactor); 
  canvas.text("Thank You! Mission Complete (THE END)", centerX - 150.0, centerY + 80.0)
  
  canvas.fill(0, 255, 255); canvas.stroke(0, 190, 255); canvas.strokeWeight(glowFactor * 0.8); 
  canvas.text("Made by Yadnesh Shahare and Bhavesh Shahare", centerX - 200.0, centerY + 30.0)
  
  canvas.fill(255, 255, 255); canvas.stroke(255, 255, 255); canvas.strokeWeight(glowFactor * 0.5); 
  canvas.text("Jai Hind! Jai Bharat!", centerX - 80.0, centerY - 20.0)

  canvas.fill(180, 100, 255); canvas.stroke(220, 150, 255); canvas.strokeWeight(glowFactor * 0.5);
  canvas.text("Next part in my brother PC", centerX - 115.0, centerY - 80.0)
}

def drawWelcomeSirScreen(canvas: CanvasDraw): Unit = {
  canvas.fill(0, 5, 20); canvas.noStroke(); canvas.rect(0, 0, cwidth, cheight)
  for (i <- 0 until 180) { canvas.stroke(255, 255, 255, 180); canvas.strokeWeight(starSize(i)); canvas.point(starX(i), starY(i)) }

  val pulseGlow = math.max(1.0, math.sin(welcomeTimer * 5) * 3 + 5)
  canvas.fill(255, 215, 0); canvas.stroke(255, 153, 51); canvas.strokeWeight(pulseGlow)
  canvas.text("WELCOME SIR", cwidth / 2.0 - 70, cheight / 2.0 + 60)
  canvas.fill(0, 255, 255); canvas.stroke(0, 180, 255); canvas.strokeWeight(pulseGlow * 0.8)
  canvas.text("TO ISRO GAGANYAAN 3D SPACE MISSION CODE", cwidth / 2.0 - 180, cheight / 2.0 + 20)
  canvas.fill(220, 240, 255); canvas.noStroke()
  canvas.text("? Features: Real Planetary Data | 3D Controls | Exoplanets System ?", cwidth / 2.0 - 210, cheight / 2.0 - 30)
  canvas.text("? Advanced Mini-Games | Dynamic Astronaut Simulations | Patriotic Finale ?", cwidth / 2.0 - 225, cheight / 2.0 - 60)
  canvas.fill(0, 255, 150)
  canvas.text(s"Initializing Mission Call Letter... (${(7.5 - welcomeTimer).max(0.0).toInt + 1}s)", cwidth / 2.0 - 130, cheight / 2.0 - 110)
}

def drawISROLetterScreen(canvas: CanvasDraw): Unit = {
  canvas.fill(5, 10, 25); canvas.noStroke(); canvas.rect(0, 0, cwidth, cheight)
  val (lx, ly, lw, lh) = (cwidth / 2.0 - 260.0, cheight / 2.0 - 150.0, 520.0, 310.0)
  canvas.fill(15, 25, 50, 240); canvas.stroke(255, 153, 51); canvas.strokeWeight(3.0); canvas.rect(lx, ly, lw, lh)
  canvas.fill(255, 215, 0); canvas.text("?? ISRO OFFICIAL INVITATION LETTER ??", lx + 100, ly + 25)
  
  if (letterTextTyped > 0) {
    val printedText = officialLetterText.substring(0, letterTextTyped)
    val lines = printedText.split("\n"); var currY = ly + 55.0
    for (line <- lines) { canvas.fill(220, 240, 255); canvas.text(line, lx + 20, currY); currY += 18.0 }
  }

  if (letterTextTyped >= officialLetterText.length) {
    val btnX = lx + 160.0; val btnY = ly - 40.0
    canvas.fill(18, 136, 7); canvas.stroke(255, 255, 255); canvas.strokeWeight(2.0); canvas.rect(btnX, btnY, 200, 35)
    canvas.fill(255, 255, 255); canvas.text("ACCEPT MISSION ?", btnX + 35, btnY + 22)
    if (isMousePressed && mouseX >= btnX && mouseX <= btnX + 200 && mouseY >= btnY && mouseY <= btnY + 35) { currentStoryStage = 1; playSoundFx(75, 250) }
  }
}

def drawPartOneTitleScreen(canvas: CanvasDraw): Unit = {
  canvas.fill(0, 0, 0); canvas.noStroke(); canvas.rect(0, 0, cwidth, cheight)
  val glow = math.max(1.0, math.sin(titleCardTimer * 4) * 3 + 5)
  canvas.fill(0, 255, 255); canvas.stroke(0, 180, 255); canvas.strokeWeight(glow); canvas.text("CHAPTER 1", cwidth / 2.0 - 45, cheight / 2.0 + 40)
  canvas.fill(255, 215, 0); canvas.stroke(255, 153, 51); canvas.strokeWeight(glow * 1.2); canvas.text("PART 1: THE ASTRONAUT", cwidth / 2.0 - 110, cheight / 2.0 - 10)
  canvas.fill(200, 200, 200); canvas.noStroke(); canvas.text("Preparing Gaganyaan Space Launch Sequence...", cwidth / 2.0 - 140, cheight / 2.0 - 60)
}

def viewState(canvas: CanvasDraw): Unit = {
  if (currentStoryStage == -3) drawRobotChallengeGameScreen(canvas)
  else if (currentStoryStage == -2) drawRobotSelectionScreen(canvas)
  else if (currentStoryStage == -1) drawWelcomeSirScreen(canvas)
  else if (currentStoryStage == 0) drawISROLetterScreen(canvas)
  else if (currentStoryStage == 1) drawPartOneTitleScreen(canvas)
  else {
    if (anthemFinished) drawCleanEndScreen(canvas)
    else {
      canvas.fill(0, 0, 0, 255); canvas.noStroke(); canvas.rect(0, 0, cwidth, cheight)
      for (i <- 0 until starCount) {
        val starAlpha = math.max(50, math.min(255, (150 + math.sin(globalTime * 5 + i) * 105).toInt))
        canvas.stroke(255, 255, 255, starAlpha); canvas.strokeWeight(math.max(0.1, starSize(i) + (math.sin(globalTime * 6 + i).toFloat * 1.1)))
        canvas.point(starX(i), starY(i))
      }
      drawParticleEffects(canvas)
      if (isParadeSceneActive) drawParadeGroundScene(canvas)
      else if (isLandingOnEarth || isLandedOnEarth) drawEarthFirstLandingScene(canvas)
      else {
        if (isLaunched) drawPlanetSurfaceBackground(canvas)
        else { drawFullSolarSystem(canvas); drawPlanetList(canvas); draw3DPlanetPreview(canvas) }
        drawLaunchAndUI(canvas); drawISRORocket(canvas)
      }
      drawQuizPopup(canvas); drawPlanetInfoPopup(canvas); drawMiniGames(canvas); drawCustomAlertPopup(canvas)
    }
  }
}

animateWithCanvasDraw { canvas => updateState(); viewState(canvas) }