Code Sketch


yadnesh shahare
By: Mhalsakant School
Category: Programming
// --- ISRO ULTIMATE MISSION: MASTERPIECE EDITION (3D ROTATABLE & ZOOMABLE) ---
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 => }
  }
}

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!"

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 = 350
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.0))

var (waveAngle, chakraRotation, neonGlowPhase, globalTime) = (0.0, 0.0, 0.0, 0.0)
var currentSolarSystemId = 1

// --- 3D ROTATION & ZOOM CONTROLS ---
var cameraRotationX = 0.5 // Tilt Angle
var cameraRotationY = 0.0 // Rotation Angle
var zoomScale = 1.0       // Zoom Level
var lastMouseX = 0.0
var lastMouseY = 0.0
var isDraggingSolarSystem = 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)
var (astronautOut, infoPanelShown, isFlagPlantedOnPlanet, isAstronautReturningIn, quizActive, isMiniGameActive, readyToReturnEarth) = (false, false, false, false, false, false, false)
var (infoPageNumber, activeGameMode) = (1, 1)
var (astroWalkToPlantX, miniGameTimer) = (0.0, 20.0)
val (plantedFlagX, plantedFlagY) = ((cwidth / 2.0) - 80.0, 155.0)

// --- PARTICLES DATA ---
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(40)(DynamicParticle(randomDouble(0, cwidth), randomDouble(cheight, cheight + 200), randomDouble(-0.5, 0.5), randomDouble(-2.0, -1.0), 1.0, cm.rgb(255, 153, 51), randomDouble(4, 7)))

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, 14.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, 16.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, 14.0)))
}

def addLandingDust(x: Double, y: Double): Unit = {
  for (_ <- 0 until 4) {
    dustParticles.append(DynamicParticle(x + randomDouble(-30, 30), y, randomDouble(-2.5, 2.5), randomDouble(0.2, 1.5), 1.0, cm.rgb(210, 180, 140), randomDouble(4.0, 9.0)))
  }
}

def triggerExplosion(x: Double, y: Double): Unit = {
  for (_ <- 0 until 25) {
    val gVal = randomDouble(50, 200).toInt
    explosionParticles.append(DynamicParticle(x, y, randomDouble(-6.0, 6.0), randomDouble(-6.0, 6.0), 1.0, cm.rgb(255, gVal, 0), randomDouble(5.0, 12.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.2
  }

  explosionParticles.filterInPlace(_.life > 0)
  explosionParticles.foreach { p =>
    p.x += p.vx
    p.y += p.vy
    p.life -= 0.05
    p.size *= 0.95
  }

  petalShower.foreach { p =>
    p.y += p.vy; p.x += math.sin(globalTime * 3 + p.y * 0.05) * 1.2
    if (p.y < 0) { p.y = cheight + 20; p.x = randomDouble(0, cwidth) }
  }
}

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 = 4
var dodgerAstX = Array.fill(dodgerCount)(cwidth / 2.0)
var dodgerAstY = Array.fill(dodgerCount)(cheight / 2.0 + 150.0)
var dodgerAstSpeed = Array.fill(dodgerCount)(3.8)
var dodgerScore = 0

var (landerX, landerY, landerVy, landerVx, landerFuel) = (cwidth / 2.0, cheight / 2.0 + 50.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 * 45.0); dodgerAstSpeed(i) = randomDouble(3.0, 5.0) }
}

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 handleSolar3DControls(): Unit = {
  if (!isLaunched && !infoPanelShown && !quizActive && !isMiniGameActive) {
    // Mouse Drag to Rotate 3D Solar System
    if (isMousePressed) {
      if (mouseX > 250 && mouseX < cwidth - 260) {
        if (isDraggingSolarSystem) {
          val dx = mouseX - lastMouseX
          val dy = mouseY - lastMouseY
          cameraRotationY += dx * 0.01
          cameraRotationX += dy * 0.01
          cameraRotationX = math.max(0.1, math.min(1.4, cameraRotationX)) // Clamp tilt angle
        }
        isDraggingSolarSystem = true
        lastMouseX = mouseX
        lastMouseY = mouseY
      }
    } else {
      isDraggingSolarSystem = false
    }

    // Keyboard Zoom Controls
    if (isKeyPressed(Kc.VK_UP)) zoomScale = math.min(2.5, zoomScale + 0.02)
    if (isKeyPressed(Kc.VK_DOWN)) zoomScale = math.max(0.4, zoomScale - 0.02)
  }
}

def updateState(): Unit = {
  if (startAnthemPhase) { waveAngle += 0.22; if (!anthemFinished) playAnthemStep(); return }
  waveAngle += 0.22; chakraRotation += 0.22; neonGlowPhase += 0.22; globalTime += 0.025
  updateExhaust()
  handleSolar3DControls()

  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.8, 0.6, 0.45, 0.4, 0.35, 0.28, 0.22, 0.16)
    for (i <- pList.indices if pList(i) == selectedPlanet) {
      val a = globalTime * (if(i < planetSpeeds.length) planetSpeeds(i) else 0.2)
      targetX = (cwidth / 2.0 - 30.0) + math.cos(a) * ((i + 1) * 20.0); targetY = (cheight / 2.0) + math.sin(a) * ((i + 1) * 12.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 {
      astronautOut = true
      if (astroWalkToPlantX < 80.0) astroWalkToPlantX += 1.2
      else if (flagPlantProgress < 1.0) { flagPlantProgress += 0.02; if (flagPlantProgress >= 1.0) isFlagPlantedOnPlanet = true }
      else { astroWaitTimer += 0.04; if (astroWaitTimer >= 1.2) { infoPanelShown = true; readyToReturnEarth = true } }
    }
  }

  if (isAstronautReturningIn) {
    if (astroWalkToPlantX > 0.0) astroWalkToPlantX -= 1.5
    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 = 9
  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, 35)
  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 drawOrbitingSatellite(canvas: CanvasDraw, cx: Double, cy: Double, dist: Double): Unit = {
  val satAngle = globalTime * 1.5
  val sx = cx + math.cos(satAngle) * dist
  val sy = cy + math.sin(satAngle) * (dist * 0.4)
  canvas.fill(200, 200, 220); canvas.noStroke(); canvas.ellipse(sx, sy, 6, 6)
  canvas.stroke(0, 200, 255); canvas.strokeWeight(1.2)
  canvas.line(sx - 8, sy, sx + 8, sy)
}

// 3D ROTATABLE AND ZOOMABLE SOLAR SYSTEM
def drawFullSolarSystem(canvas: CanvasDraw): Unit = {
  val (centerX, centerY) = (cwidth / 2.0 - 10.0, cheight / 2.0 - 10.0)
  canvas.noFill(); val pList = getActivePlanetList()

  // Instruction Overlay
  canvas.fill(255, 255, 255, 180)
  canvas.text("?? Drag Mouse to Rotate 3D Solar System | ???? Arrow Keys to Zoom", centerX - 220, cheight - 25)

  // Render Orbits with 3D Angles
  for (i <- pList.indices) { 
    val alphaVal = math.max(0, math.min(255, (80 + math.sin(globalTime * 3 + i) * 45).toInt))
    canvas.stroke(0, 220, 255, alphaVal)
    canvas.strokeWeight(1.2)
    val baseDist = (i * 26.0 + 45.0) * zoomScale
    val ew = math.max(0.1, baseDist * 2)
    val eh = math.max(0.1, baseDist * 2 * cameraRotationX)
    canvas.ellipse(centerX, centerY, ew, eh) 
  }

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

  val planetSpeeds = Array(0.8, 0.6, 0.45, 0.4, 0.35, 0.28, 0.22, 0.16); val activeMap = getActivePlanetDetailsMap()
  for (i <- pList.indices) {
    val planetName = pList(i); 
    val angle = (globalTime * (if(i < planetSpeeds.length) planetSpeeds(i) else 0.2)) + cameraRotationY
    val dist = (i * 26.0 + 45.0) * zoomScale
    val curX = centerX + math.cos(angle) * dist
    val curY = centerY + math.sin(angle) * (dist * cameraRotationX)
    
    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 baseSize = if (Array("Earth", "Kepler-22b", "Kepler-452b").contains(planetName)) 18.0 else (12.0 + (i * 0.4))
    val pSize = math.max(1.0, baseSize * zoomScale)
    
    // --- ???????? ??????????? ???? ?????? ???? (Thin White Ring for All Planets) ---
    canvas.noFill()
    canvas.stroke(255, 255, 255, 180) // ?????? ???
    canvas.strokeWeight((1.0 * zoomScale).toFloat) // ???? ???? ??????? ????
    val thinRingW = pSize * 2.2
    val thinRingH = math.max(0.1, pSize * 0.6 * cameraRotationX)
    canvas.ellipse(curX, curY, thinRingW, thinRingH)

    // ??? Saturn ?? ????? ???? ?????????
    if (pRing) { 
      canvas.noFill(); canvas.stroke(245, 220, 180, 210); canvas.strokeWeight((2.5 * zoomScale).toFloat); 
      val ringW = pSize * (2.4 + math.sin(globalTime * 2) * 0.1)
      canvas.ellipse(curX, curY, ringW, math.max(0.1, pSize * 0.8 * cameraRotationX)) 
    }

    draw3DSphere(canvas, curX, curY, pSize, pCol, pAccent, globalTime * 3 + i)

    if (!planetLocked && isMousePressed && !isDraggingSolarSystem && 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
    }

    // ?????????? ???????????? ???? ???? (Cyan) ????, ?????? ??????? ????? ???.
    if (planetName == selectedPlanet) { 
      canvas.noFill(); canvas.stroke(0, 255, 255, 220); 
      val glowW = math.max(1.0, 1.5 + math.sin(neonGlowPhase * 6).toFloat * 1.0)
      canvas.strokeWeight(glowW)
      canvas.ellipse(curX, curY, pSize + 12, math.max(0.1, (pSize + 12) * cameraRotationX)) 
    }
  }
}

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)
  
  val switchPulse = math.max(0, math.min(255, (math.sin(globalTime * 4) * 30 + 180).toInt))
  canvas.fill(0, switchPulse, 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 } }
  
  // Hologram ????? ?????? ???? ?????? ????
  canvas.noFill(); canvas.stroke(255, 255, 255, 180); canvas.strokeWeight(1.5); canvas.ellipse(cx, cy, 175, 45)
  if (pRings) { canvas.noFill(); canvas.stroke(220, 210, 180, 230); canvas.strokeWeight(6.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); canvas.stroke(255, 255, 255); canvas.strokeWeight(0.6); canvas.point(chakraX, chakraY)
}

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 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)
  
  val reflectAlpha = math.max(0, math.min(255, (math.sin(globalTime * 8) * 40 + 60).toInt))
  canvas.fill(255, 255, 255, reflectAlpha); 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))
    if (flagPlantProgress > 0.0 && !isFlagPlantedOnPlanet) drawWavingTricolorFlagWithAshokChakra(canvas, -80.0, -20.0)
    if (astronautOut && !isLandedOnEarth) {
      canvas.pushMatrix(); canvas.translate(-astroWalkToPlantX, -30.0)
      drawAstronautPlayer(canvas, 0, 0)
      if (astroWalkToPlantX >= 80.0 && flagPlantProgress < 1.0) { canvas.stroke(255, 255, 255); canvas.strokeWeight(3.0); canvas.line(-5, 0, -15, 10) }
      canvas.popMatrix()
    }
  }
  canvas.popMatrix()
}

def drawAstronautPlayer(canvas: CanvasDraw, px: Double, py: Double): Unit = {
  val legAnim = math.sin(globalTime * 12) * 3.0
  canvas.fill(245, 245, 255); canvas.stroke(0, 255, 255); canvas.strokeWeight(1.5); canvas.rect(px - 8, py - 12, 16, 24)
  canvas.stroke(200, 200, 200); canvas.strokeWeight(3)
  canvas.line(px - 4, py - 12, px - 4 + legAnim, py - 20)
  canvas.line(px + 4, py - 12, px + 4 - legAnim, py - 20)
  canvas.fill(255, 255, 255); canvas.ellipse(px, py - 18, 14, 14)
  val visorBlink = if (math.sin(globalTime * 8) > 0) cm.rgb(0, 190, 250) else cm.rgb(0, 240, 255)
  canvas.fill(visorBlink); canvas.noStroke(); canvas.ellipse(px, py - 18, 9, 7)
}

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 >= iy + 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 >= iy + 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", centerX - 110.0, centerY + 60.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)
  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 - 60.0)
}

def viewState(canvas: CanvasDraw): Unit = {
  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)
      val starW = math.max(0.1, starSize(i) + (math.sin(globalTime * 6 + i).toFloat * 1.1))
      canvas.strokeWeight(starW)
      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) }