Pyroflame

SpaceVerse -Segoa V5.6.2JM - Mamacita(C)(TM)(N)

Aug 13th, 2025
659
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 55.38 KB | None | 0 0
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8"/>
  5. <meta name="viewport" content="width=device-width,initial-scale=1"/>
  6. <title>SpaceVerse: Segoa v5.6.2</title>
  7. <style>
  8. :root {
  9.   --bg: #010214;
  10.   --panel: rgba(2,6,12,0.78);
  11.   --muted: #9aa;
  12.   --accent: #7ef;
  13.   --teal: #6ee7b7;
  14.   --danger: #ff6b6b;
  15.   --gold: #ffd166;
  16.   --purple: #a78bfa;
  17. }
  18.  
  19. * { box-sizing: border-box; }
  20. html, body { height: 100%; margin: 0; background: var(--bg); color: #dfe; overflow: hidden; font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto; }
  21. canvas { display: block; width: 100vw; height: 100vh; cursor: crosshair; background: linear-gradient(180deg, #000011, #02031a); }
  22.  
  23. #hud { position: fixed; left: 12px; top: 12px; z-index: 60; background: var(--panel); padding: 12px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.04); backdrop-filter: blur(4px); }
  24. #hud .title { font-weight: 800; color: var(--accent); margin-bottom: 6px; font-size: 14px; }
  25. #hud .row { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 6px; }
  26. #hud button { background: #07202a; border: none; color: #bfe; padding: 8px 12px; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 12px; transition: background-color .2s, transform .12s; }
  27. #hud button:hover { background: #0a2a36; transform: translateY(-1px); }
  28. #hud small { display: block; color: var(--muted); margin-top: 6px; font-size: 11px; line-height: 1.3; }
  29.  
  30. #mega { position: fixed; right: 12px; top: 12px; width: 360px; max-height: calc(100vh - 24px); overflow-y: auto; z-index: 80; background: linear-gradient(180deg, rgba(6,10,16,0.95), rgba(3,6,10,0.85)); border-radius: 12px; padding: 12px; border: 1px solid rgba(255,255,255,0.03); display: none; backdrop-filter: blur(10px); }
  31. #mega h2 { margin: 0 0 12px 0; color: #8ef; font-size: 16px; }
  32. .spawn-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
  33. .spawn-btn { flex: 1 1 calc(50% - 3px); background: #06101a; color: #bfe; padding: 8px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.03); cursor: pointer; font-weight: 600; font-size: 12px; text-align: center; transition: all .2s; }
  34. .spawn-btn:hover { background: #0a1520; border-color: rgba(255,255,255,0.08); transform: translateY(-1px); }
  35.  
  36. #debug { position: fixed; left: 12px; bottom: 12px; color: var(--muted); font-size: 12px; z-index: 55; background: rgba(2,6,12,0.5); padding: 8px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.02); }
  37.  
  38. .pie-container { position: fixed; z-index: 120; user-select: none; pointer-events: none; left: 0; top: 0; transform: translate(-10000px, -10000px); will-change: transform, opacity; }
  39. .pie-wrap { position: relative; width: 1px; height: 1px; pointer-events: none; animation: pieIn .12s ease-out both; }
  40. @keyframes pieIn { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } }
  41. .pie-slice { position: absolute; width: 150px; height: 46px; display: flex; align-items: center; justify-content: center; pointer-events: auto; cursor: pointer; border-radius: 10px; padding: 6px; box-shadow: 0 6px 18px rgba(0,0,0,0.5); transform-origin: center center; transition: transform .1s ease-out, filter .12s; backdrop-filter: blur(4px); }
  42. .pie-slice .label { color: #fff; font-weight: 800; text-shadow: 0 0 6px rgba(0,0,0,0.6); font-size: 13px; text-align: center; }
  43. .pie-slice:hover { transform: scale(1.08); }
  44. .pie-divider { position: absolute; left: -2px; top: -2px; right: -2px; bottom: -2px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.06); pointer-events: none; }
  45.  
  46. #terminal { position: fixed; bottom: 12px; left: 50%; transform: translateX(-50%); width: 80%; max-width: 600px; background: rgba(2,6,12,0.9); padding: 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.04); display: none; z-index: 100; font-family: monospace; font-size: 14px; color: var(--teal); }
  47. #terminal input { width: 100%; background: transparent; border: none; color: var(--teal); font-family: monospace; font-size: 14px; outline: none; padding: 8px; }
  48. #terminal-suggestions { position: absolute; bottom: 100%; left: 0; right: 0; background: rgba(2,6,12,0.95); border-radius: 8px 8px 0 0; border: 1px solid rgba(255,255,255,0.04); max-height: 200px; overflow-y: auto; }
  49. #terminal-suggestions div { padding: 6px 12px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.05); }
  50. #terminal-suggestions div:hover { background: rgba(255,255,255,0.1); }
  51.  
  52. .selection-box { position: fixed; border: 1px dashed #7ef; background: rgba(126,239,255,0.1); pointer-events: none; z-index: 50; }
  53.  
  54. #shipPanel { position: fixed; right: 12px; bottom: 12px; width: 300px; background: rgba(2,6,12,0.9); border-radius: 8px; padding: 12px; border: 1px solid rgba(255,255,255,0.04); z-index: 70; display: none; backdrop-filter: blur(4px); }
  55. #shipPanel h3 { margin: 0 0 8px 0; color: var(--accent); font-size: 14px; }
  56. #shipPanel .crew-list { margin-top: 8px; max-height: 200px; overflow-y: auto; }
  57. .crew-member { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid rgba(255,255,255,0.05); }
  58. .crew-role { color: var(--teal); font-weight: 600; }
  59. .crew-stats { color: var(--muted); font-size: 12px; }
  60.  
  61. #missionPanel { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 80%; max-width: 500px; background: rgba(2,6,12,0.95); padding: 16px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.06); z-index: 90; display: none; backdrop-filter: blur(8px); max-height: 80vh; overflow-y: auto; }
  62. #missionPanel h2 { color: var(--gold); margin-top: 0; }
  63. .mission-card { background: rgba(0,0,0,0.3); border-radius: 8px; padding: 12px; margin-bottom: 12px; }
  64. .mission-card h3 { margin: 0 0 8px 0; color: var(--accent); }
  65. .mission-card p { margin: 4px 0; color: var(--muted); }
  66. .mission-card button { background: var(--purple); border: none; color: white; padding: 6px 12px; border-radius: 6px; cursor: pointer; }
  67.  
  68. #statsPanel {
  69.   position: fixed; left: 12px; top: 50%; transform: translateY(-50%); width: 220px;
  70.   background: var(--panel); padding: 12px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.04);
  71.   backdrop-filter: blur(4px); z-index: 59; font-size: 12px;
  72. }
  73. #statsPanel h3 { margin: 0 0 10px 0; color: var(--accent); font-size: 14px; text-align: center; }
  74. .stats-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 8px; align-items: center; }
  75. .stats-grid .label { color: var(--muted); }
  76. .stats-grid .value { text-align: right; color: var(--teal); font-weight: 600; }
  77.  
  78. @media (max-width: 640px) {
  79.   .pie-slice { width: 120px; height: 44px; }
  80.   #mega, #shipPanel { width: 92%; right: 4%; left: 4%; }
  81.   #missionPanel { width: 90%; }
  82.   #statsPanel { display: none; } /* Hide stats on small screens */
  83. }
  84. </style>
  85. </head>
  86. <body>
  87. <canvas id="c" tabindex="0" aria-label="Gameplay canvas"></canvas>
  88.  
  89. <div id="hud" role="toolbar" aria-label="HUD Controls">
  90.   <div class="title">SpaceVerse β€” v5.6.2</div>
  91.   <div class="row">
  92.     <button id="btnPause">Pause (P)</button>
  93.     <button id="btnCenter">Center View</button>
  94.     <button id="btnClear">Clear All</button>
  95.     <button id="btnMegaToggle">Toggle Menu (M)</button>
  96.     <button id="btnTerminal">Terminal (~)</button>
  97.     <button id="btnMissions">Missions (J)</button>
  98.     <button id="btnShipPanel">Ship Info (I)</button>
  99.   </div>
  100.   <small>
  101.     Right-click = Player pie | Ctrl/Cmd+Right-click = Enemy pie | Alt+Right-click = Astro pie | Shift+Right-click = Structures<br>
  102.     Left-click = select | Drag left-click = multi-select | WASD = control selected ships | Space = auto-fire | F = cycle follow | ~ = terminal | Z+Right-click = Z menu
  103.   </small>
  104. </div>
  105.  
  106. <div id="statsPanel">
  107.   <h3>Faction Stats</h3>
  108.   <div class="stats-grid" id="statsGrid">
  109.     <!-- Stats will be populated by script -->
  110.   </div>
  111. </div>
  112.  
  113. <div id="mega" aria-hidden="true">
  114.   <h2>πŸš€ Mega Spawner</h2>
  115.   <div class="spawn-row"><button class="spawn-btn" data-action="playerMothership">πŸ›Έ Player Mothership</button><button class="spawn-btn" data-action="enemyMothership">πŸ‘Ύ Enemy Mothership</button></div>
  116.   <div class="spawn-row"><button class="spawn-btn" data-action="fighter">⚑ Fighter</button><button class="spawn-btn" data-action="brawler">πŸ’ͺ Brawler</button></div>
  117.   <div class="spawn-row"><button class="spawn-btn" data-action="sniper">🎯 Sniper</button><button class="spawn-btn" data-action="support">πŸ”§ Support</button></div>
  118.   <div class="spawn-row"><button class="spawn-btn" data-action="kamikaze">πŸ’₯ Kamikaze</button><button class="spawn-btn" data-action="asteroids">β˜„οΈ Asteroid Field</button></div>
  119.   <div class="spawn-row"><button class="spawn-btn" data-action="star">⭐ Star</button><button class="spawn-btn" data-action="planet">πŸͺ Planet</button></div>
  120.   <div style="font-size:12px;color:#9aa;margin-top:8px">β€’ Mega spawns near camera center; pies spawn at your cursor.</div>
  121. </div>
  122.  
  123. <div id="terminal" role="dialog" aria-label="Command Terminal">
  124.   <div id="terminal-suggestions"></div>
  125.   <input type="text" id="terminalInput" placeholder="Enter command (e.g., spawn fighter 5, clear, stats)" autocomplete="off">
  126. </div>
  127.  
  128. <div id="shipPanel">
  129.   <h3>Ship Information</h3>
  130.   <div id="shipInfo"></div>
  131.   <h3>Crew <small id="crewCount"></small></h3>
  132.   <div class="crew-list" id="crewList"></div>
  133. </div>
  134.  
  135. <div id="missionPanel">
  136.   <h2>Mission Board</h2>
  137.   <div id="missionList"></div>
  138.   <button id="closeMissions" style="margin-top:12px;">Close</button>
  139. </div>
  140.  
  141. <div id="debug" aria-live="polite">Entities: 0 β€” Projectiles: 0 β€” FPS: 0</div>
  142.  
  143. <script>
  144. (() => {
  145.   const CFG = {
  146.     GRAVITY_BASE: 0.0007, MAX_PROJECTILES: 1000, MAX_ENTITIES: 6000,
  147.     POOL_PROJECTILES: 1400, SPATIAL_CELL: 220, DT_MAX: 0.05, FPS_INTERVAL: 1000,
  148.     CREW_SKILLS: ['Pilot', 'Co-Pilot', 'Mechanic', 'Weapons', 'Scientist', 'Engineer', 'Medic', 'Navigator']
  149.   };
  150.  
  151.   const c = document.getElementById('c');
  152.   const ctx = c.getContext('2d', { alpha: false });
  153.   let DPR = Math.max(1, window.devicePixelRatio || 1);
  154.   function resize() {
  155.     DPR = Math.max(1, window.devicePixelRatio || 1);
  156.     c.width = Math.floor(window.innerWidth * DPR);
  157.     c.height = Math.floor(window.innerHeight * DPR);
  158.     c.style.width = window.innerWidth + 'px';
  159.     c.style.height = window.innerHeight + 'px';
  160.     ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
  161.   }
  162.   window.addEventListener('resize', resize);
  163.   resize();
  164.  
  165.   const W = () => window.innerWidth;
  166.   const H = () => window.innerHeight;
  167.   const TAU = Math.PI * 2;
  168.   const rand = (r=1) => (Math.random()-0.5) * 2 * r;
  169.   const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
  170.   const hexToRGBA = (hex, a=1) => {
  171.     if (!hex) return `rgba(255,255,255,${a})`;
  172.     const h = hex.replace('#', '');
  173.     const r = parseInt(h.slice(0,2), 16), g = parseInt(h.slice(2,4), 16), b = parseInt(h.slice(4,6), 16);
  174.     return `rgba(${r},${g},${b},${a})`;
  175.   };
  176.  
  177.   let entities = [], nextId = 1, running = true, selectedIds = new Set(), followId = null;
  178.   let projCount = 0, lastTime = performance.now(), fps = 0, frameCount = 0, lastFpsTime = performance.now();
  179.   const camera = { x: 0, y: 0, targetX: 0, targetY: 0, lerp: 0.08 };
  180.   const mouse = { screenX: W()/2, screenY: H()/2, x: 0, y: 0, down: false, btn: -1, startX: 0, startY: 0 };
  181.   const TEAL = 'TEAL', RED = 'RED';
  182.   let factions = {}, sideFactions = [];
  183.   let isDragging = false, selectionBox = null;
  184.   let missions = [];
  185.   let terminalHistory = [];
  186.   let terminalHistoryIndex = -1;
  187.  
  188.   const crewNames = ["Alex", "Jamie", "Taylor", "Jordan", "Casey", "Riley", "Quinn", "Morgan", "Avery", "Skyler", "Dakota", "Peyton", "Rowan", "Charlie", "Emerson", "Finley", "Sawyer", "Hayden", "Kai", "River"];
  189.  
  190.   function generateCrew(shipSize) {
  191.     const crewSize = Math.max(1, Math.floor(shipSize < 10 ? 3 + Math.random() * 2 : shipSize < 20 ? 8 + Math.random() * 4 : shipSize < 30 ? 20 + Math.random() * 10 : 50 + Math.random() * 50));
  192.    const crew = [];
  193.    for (let i = 0; i < crewSize; i++) {
  194.      crew.push({
  195.        id: nextId++, name: crewNames[Math.floor(Math.random() * crewNames.length)],
  196.        role: CFG.CREW_SKILLS[Math.floor(Math.random() * CFG.CREW_SKILLS.length)],
  197.        skill: Math.floor(1 + Math.random() * 5), morale: Math.floor(50 + Math.random() * 50),
  198.        salary: 10 + Math.floor(Math.random() * 40)
  199.      });
  200.    }
  201.    return crew;
  202.  }
  203.  
  204.  function generateMissions() {
  205.    const missionTypes = [
  206.      { name: "Supply Run", desc: "Deliver supplies to a remote outpost.", reward: { money: 500, supplies: 10, xp: 100 }, difficulty: 1, timeLimit: 300 },
  207.      { name: "Pirate Hunt", desc: "Eliminate pirate threats in this sector.", reward: { money: 800, crypto: 50, xp: 150 }, difficulty: 2, timeLimit: 420 },
  208.      { name: "Mineral Scan", desc: "Scan uncharted asteroids for valuable minerals.", reward: { minerals: 25, chips: 5, xp: 200 }, difficulty: 2, timeLimit: 480 },
  209.      { name: "Escort Duty", desc: "Protect a merchant convoy through a dangerous route.", reward: { money: 1200, gifts: 1, xp: 250 }, difficulty: 3, timeLimit: 600 },
  210.      { name: "Emergency Rescue", desc: "Save stranded crew from a disabled vessel.", reward: { money: 2000, food: 20, fluids: 20, xp: 300 }, difficulty: 4, timeLimit: 360 }
  211.    ];
  212.    missions = [];
  213.    for (let i = 0; i < 5; i++) {
  214.      const type = missionTypes[Math.floor(Math.random() * missionTypes.length)];
  215.      missions.push({ ...type, id: nextId++, location: { x: rand(W() * 0.8), y: rand(H() * 0.8) }, faction: Math.random() > 0.5 ? TEAL : RED, accepted: false, progress: 0, startTime: 0 });
  216.     }
  217.   }
  218.  
  219.   function initFactions() {
  220.     const defaultResources = () => ({
  221.       money: 10000, crypto: 100, supplies: 50, chips: 20,
  222.       gauss: 5, nitro: 5, minerals: 0, gifts: 0, food: 100, fluids: 100,
  223.     });
  224.     factions = {
  225.       [TEAL]: { id: TEAL, name: 'Player', color: '#6ee7b7', members: [], reputation: 50, resources: defaultResources() },
  226.       [RED]: { id: RED, name: 'Enemy', color: '#ff6b6b', members: [], reputation: 50, resources: defaultResources() }
  227.     };
  228.     sideFactions = [
  229.       { id: 'PIRATES', name: 'Pirates', color: '#ffd166', reputation: 0, resources: defaultResources() },
  230.       { id: 'MERCHANTS', name: 'Merchants', color: '#a78bfa', reputation: 50, resources: defaultResources() }
  231.     ];
  232.   }
  233.  
  234.   const projPool = [];
  235.   function poolInit() { projPool.length = 0; for (let i = 0; i < CFG.POOL_PROJECTILES; i++) projPool.push({ _pooled: true }); }
  236.  function projAcquire() { return projPool.length ? projPool.pop() : { _pooled: false }; }
  237.  function projRelease(p) { p._pooled = true; p.kind = 'free'; p.ownerId = 0; p.ownerSide = 'NEUTRAL'; projPool.push(p); }
  238.  
  239.  const spatial = new Map();
  240.  const spatialKey = (cx, cy) => `${cx},${cy}`;
  241.   function spatialClear() { spatial.clear(); }
  242.   function spatialInsert(e) {
  243.     const cell = CFG.SPATIAL_CELL;
  244.     const minX = Math.floor((e.x - (e.size || 0)) / cell); const maxX = Math.floor((e.x + (e.size || 0)) / cell);
  245.     const minY = Math.floor((e.y - (e.size || 0)) / cell); const maxY = Math.floor((e.y + (e.size || 0)) / cell);
  246.     for (let cx = minX; cx <= maxX; cx++) for (let cy = minY; cy <= maxY; cy++) {
  247.      const k = spatialKey(cx, cy); if (!spatial.has(k)) spatial.set(k, []);
  248.      spatial.get(k).push(e);
  249.    }
  250.  }
  251.  function spatialQueryRegion(x, y, radius) {
  252.    const cell = CFG.SPATIAL_CELL;
  253.    const minX = Math.floor((x - radius) / cell), maxX = Math.floor((x + radius) / cell);
  254.    const minY = Math.floor((y - radius) / cell), maxY = Math.floor((y + radius) / cell);
  255.    const set = new Set();
  256.    for (let cx = minX; cx <= maxX; cx++) for (let cy = minY; cy <= maxY; cy++) {
  257.      const k = spatialKey(cx, cy), arr = spatial.get(k); if (arr) for (const e of arr) set.add(e);
  258.    }
  259.    return Array.from(set);
  260.  }
  261.  
  262.  function spawn(obj) {
  263.    if (entities.length >= CFG.MAX_ENTITIES) return null;
  264.     const defaults = {
  265.       id: nextId++, kind: 'unknown', x: Math.random() * W(), y: Math.random() * H(),
  266.       vx: 0, vy: 0, angle: Math.random() * TAU, size: 8, hp: 100, maxHp: 100,
  267.       side: 'NEUTRAL', archetype: 'generic', created: performance.now()
  268.     };
  269.     const e = Object.assign({}, defaults, obj);
  270.     if (e.kind === 'ship') {
  271.       e.crew = generateCrew(e.size); e.weapons = [];
  272.       if (e.archetype === 'fighter') e.weapons.push({ type: 'laser', damage: 20, range: 300, rate: 0.5 });
  273.       if (e.archetype === 'brawler') e.weapons.push({ type: 'cannon', damage: 40, range: 150, rate: 1.0 });
  274.       if (e.archetype === 'sniper') e.weapons.push({ type: 'railgun', damage: 60, range: 500, rate: 1.5 });
  275.       if (e.subtype === 'mothership') {
  276.         e.weapons.push({ type: 'missile', damage: 80, range: 400, rate: 2.0 });
  277.         e.weapons.push({ type: 'laser', damage: 30, range: 350, rate: 0.8 });
  278.       }
  279.       e.modules = []; if (e.size > 15) e.modules.push('shield'); if (e.size > 20) e.modules.push('cloak');
  280.     }
  281.     entities.push(e); if (e.side && factions[e.side]) factions[e.side].members.push(e.id);
  282.     return e;
  283.   }
  284.  
  285.   const spawnActions = {
  286.     playerMothership: (x, y) => spawn({ kind: 'ship', subtype: 'mothership', archetype: 'carrier', side: TEAL, playerControl: true, mass: 5200, size: 30, hp: 2500, maxHp: 2500, shield: 600, maxShield: 600, x, y, spawnTimer: 0, spawnRate: 2.8, name: "TEAL Command" }),
  287.     enemyMothership: (x, y) => spawn({ kind: 'ship', subtype: 'mothership', archetype: 'aggressive', side: RED, mass: 5200, size: 30, hp: 2600, maxHp: 2600, shield: 300, maxShield: 300, x, y, spawnTimer: 0, spawnRate: 5.0, name: "RED Flagship" }),
  288.     fighter: (x, y, side = RED) => spawn({ kind: 'ship', subtype: 'ship', archetype: 'fighter', side, mass: 160, size: 9, hp: 160, maxHp: 160, shield: 40, maxShield: 40, speed: 2.2, fireRate: 0.45, weapon: 'bullet', range: 280, damage: 22, x, y, name: side === TEAL ? "TEAL Interceptor" : "RED Fighter" }),
  289.     brawler: (x, y, side = RED) => spawn({ kind: 'ship', subtype: 'ship', archetype: 'brawler', side, mass: 300, size: 12, hp: 360, maxHp: 360, speed: 1.6, fireRate: 0.25, weapon: 'ram', range: 28, damage: 90, x, y, name: side === TEAL ? "TEAL Defender" : "RED Brawler" }),
  290.     sniper: (x, y, side = RED) => spawn({ kind: 'ship', subtype: 'ship', archetype: 'sniper', side, mass: 140, size: 8, hp: 120, maxHp: 120, shield: 30, maxShield: 30, speed: 1.9, fireRate: 1.2, weapon: 'laser', range: 620, damage: 46, x, y, name: side === TEAL ? "TEAL Marksman" : "RED Sniper" }),
  291.     support: (x, y, side = RED) => spawn({ kind: 'ship', subtype: 'ship', archetype: 'support', side, mass: 200, size: 10, hp: 170, maxHp: 170, shield: 80, maxShield: 80, speed: 1.6, fireRate: 1.0, weapon: 'repair', range: 160, damage: -20, x, y, name: side === TEAL ? "TEAL Medic" : "RED Support" }),
  292.     kamikaze: (x, y, side = RED) => spawn({ kind: 'ship', subtype: 'ship', archetype: 'kamikaze', side, mass: 120, size: 8, hp: 80, maxHp: 80, speed: 3.0, weapon: 'ram', range: 18, damage: 260, x, y, name: side === TEAL ? "TEAL Scout" : "RED Kamikaze" }),
  293.     star: (x, y) => spawn({ kind: 'celestial', subtype: 'star', side: 'NEUTRAL', x, y, mass: 250000, color: '#ffd166', size: 30, fixed: true, name: ["Sol", "Alpha", "Beta", "Gamma", "Delta"][Math.floor(Math.random() * 5)] + " " + ["Prime", "Secundus", "Tertius", "Quartus"][Math.floor(Math.random() * 4)] }),
  294.     planet: (x, y) => spawn({ kind: 'celestial', subtype: 'planet', side: 'NEUTRAL', x, y, mass: 14000, color: '#7fb', size: 18, name: ["Terra", "Mars", "Venus", "Jupiter", "Saturn"][Math.floor(Math.random() * 5)] + " " + ["IV", "V", "VI", "VII", "VIII"][Math.floor(Math.random() * 5)] }),
  295.     asteroids: (x, y) => { for (let i = 0; i < 24; i++) spawn({ kind: 'celestial', subtype: 'asteroid', side: 'NEUTRAL', x: x + rand(160), y: y + rand(160), mass: 20 + Math.random() * 160, color: '#b7a', size: rand(4) + 3 }); },
  296.    shieldGen: (x, y, side = TEAL) => spawn({ kind: 'structure', subtype: 'shieldGen', side, x, y, size: 18, hp: 900, maxHp: 900, shield: 1200, localRadius: 220, regen: 6 }),
  297.     missileBattery: (x, y, side = RED) => spawn({ kind: 'structure', subtype: 'missileBattery', side, x, y, size: 16, hp: 600, maxHp: 600, reloadTime: 3.5 }),
  298.     nukeDrop: (x, y, side = RED) => spawn({ kind: 'structure', subtype: 'nukeDevice', side, x, y, size: 14, hp: 300, maxHp: 300, armed: false, countdown: 1.5 }),
  299.     capitalCannon: (x, y, side = RED) => spawn({ kind: 'structure', subtype: 'capitalCannon', side, x, y, size: 22, hp: 1500, maxHp: 1500, reloadTime: 5.0, range: 800, damage: 450 }),
  300.     rebelShieldGen: (x, y) => spawn({ kind: 'structure', subtype: 'rebelShieldGen', side: RED, x, y, size: 20, hp: 1200, maxHp: 1200, shield: 1500, localRadius: 250, regen: 8, color: '#e74c3c' }),
  301.     tradersDen: (x, y) => spawn({ kind: 'structure', subtype: 'tradersDen', side: 'NEUTRAL', x, y, size: 16, hp: 500, maxHp: 500, color: '#f1c40f' }),
  302.     enclaveSatellite: (x, y) => spawn({ kind: 'structure', subtype: 'enclaveSatellite', side: 'NEUTRAL', x, y, size: 12, hp: 400, maxHp: 400, color: '#bdc3c7' }),
  303.     seaPlanet: (x, y) => spawn({ kind: 'celestial', subtype: 'planet', side: 'NEUTRAL', x, y, mass: 16000, color: '#3498db', size: 20, name: ["Aqua", "Maris", "Oceanus", "Neptun", "Thalassa"][Math.floor(Math.random() * 5)] }),
  304.     lushPlanet: (x, y) => spawn({ kind: 'celestial', subtype: 'planet', side: 'NEUTRAL', x, y, mass: 14000, color: '#2ecc71', size: 18, name: ["Verdant", "Flora", "Gaia", "Eden", "Arbor"][Math.floor(Math.random() * 5)] }),
  305.     flamePlanet: (x, y) => spawn({ kind: 'celestial', subtype: 'planet', side: 'NEUTRAL', x, y, mass: 18000, color: '#e74c3c', size: 22, name: ["Inferno", "Pyros", "Ignis", "Volcan", "Magmus"][Math.floor(Math.random() * 5)] }),
  306.     earthPlanet: (x, y) => spawn({ kind: 'celestial', subtype: 'planet', side: 'NEUTRAL', x, y, mass: 15000, color: '#2c3e50', size: 19, name: ["Terra Nova", "Earth Two", "New Eden", "Paradise", "Homestead"][Math.floor(Math.random() * 5)] })
  307.   };
  308.  
  309.   initFactions();
  310.   poolInit();
  311.   generateMissions();
  312.  
  313.   function spawnProjectile(x, y, vx, vy, owner, type = 'bullet', life = 2.0, damage = 18) {
  314.     if (projCount >= CFG.MAX_PROJECTILES) return null;
  315.     const p = projAcquire();
  316.     Object.assign(p, { kind: 'proj', subtype: type, x, y, vx, vy, ownerId: owner ? owner.id : 0, ownerSide: owner ? owner.side : 'NEUTRAL', life, damage, size: (type === 'laser' ? 2 : 2), created: performance.now() });
  317.     entities.push(p); projCount++; return p;
  318.   }
  319.   function removeEntityByIndex(i) {
  320.     const e = entities[i]; if (!e) return;
  321.     if (e.kind === 'proj') { projCount = Math.max(0, projCount - 1); if (e._pooled !== undefined) projRelease(e); }
  322.     else if (e.side && factions[e.side]) { const idx = factions[e.side].members.indexOf(e.id); if (idx >= 0) factions[e.side].members.splice(idx, 1); }
  323.     entities.splice(i, 1); selectedIds.delete(e.id);
  324.   }
  325.   function removeEntityById(id) { const i = entities.findIndex(e => e.id === id); if (i >= 0) removeEntityByIndex(i); }
  326.   function mkFX(type, x, y, color = '#fff') { spawn({ kind: 'fx', subtype: type, x, y, t: 0, life: type === 'bigExpl' ? 1.2 : 0.28, color }); }
  327.   function explode(x, y, radius = 120, damage = 500) {
  328.     const near = spatialQueryRegion(x, y, radius);
  329.     for (const s of near) {
  330.       if (!s || (s.kind !== 'ship' && s.kind !== 'structure')) continue;
  331.       const d = Math.hypot(s.x - x, s.y - y);
  332.       if (d < radius) s.hp -= damage * (1 - d / radius);
  333.    }
  334.    mkFX('bigExpl', x, y);
  335.  }
  336.  function destroyShip(s) { explode(s.x, s.y, (s.size || 12) * 4, (s.maxHp || 100) * 0.45); const idx = entities.indexOf(s); if (idx >= 0) removeEntityByIndex(idx); }
  337.   function chooseTarget(source) {
  338.     const cand = entities.filter(e => e.kind === 'ship' && e.side !== source.side && e !== source);
  339.     if (!cand.length) return null;
  340.     cand.sort((a, b) => Math.hypot(a.x - source.x, a.y - source.y) - Math.hypot(b.x - source.x, b.y - source.y));
  341.     return cand[0];
  342.   }
  343.  
  344.   function updateMissions(dt) {
  345.     const now = performance.now();
  346.     for (const mission of missions) {
  347.       if (mission.accepted && mission.startTime === 0) mission.startTime = now;
  348.       if (mission.accepted && mission.startTime > 0) {
  349.        const elapsed = (now - mission.startTime) / 1000;
  350.         mission.progress = Math.min(1, elapsed / mission.timeLimit);
  351.         if (elapsed >= mission.timeLimit) {
  352.           mission.accepted = false;
  353.           if (mission.faction === TEAL) {
  354.             factions[TEAL].reputation = Math.min(100, factions[TEAL].reputation + 5);
  355.             // Add rewards
  356.             for(const resource in mission.reward) {
  357.               if (factions[TEAL].resources.hasOwnProperty(resource)) {
  358.                 factions[TEAL].resources[resource] += mission.reward[resource];
  359.               }
  360.             }
  361.           }
  362.           mkFX('spark', mission.location.x, mission.location.y, '#6ee7b7');
  363.         }
  364.       }
  365.     }
  366.   }
  367.  
  368.   function step(dt) {
  369.     dt = Math.min(dt, CFG.DT_MAX);
  370.     spatialClear(); for (const e of entities) spatialInsert(e);
  371.     const celestials = entities.filter(e => e.kind === 'celestial');
  372.     const ships = entities.filter(e => e.kind === 'ship');
  373.     updateMissions(dt);
  374.  
  375.     for (const s of ships) {
  376.       let ax = 0, ay = 0;
  377.       for (const c of celestials) { const dx = c.x - s.x, dy = c.y - s.y, r2 = dx * dx + dy * dy + 100; const inv = 1 / Math.sqrt(r2); const f = CFG.GRAVITY_BASE * c.mass * (s.mass || 1) / r2; ax += f * dx * inv / (s.mass || 1); ay += f * dy * inv / (s.mass || 1); }
  378.       s.vx += ax * dt * 60; s.vy += ay * dt * 60;
  379.     }
  380.  
  381.     for (const s of ships) {
  382.       s.lastShot = s.lastShot || 0;
  383.       if (!s.playerControl) {
  384.         if (!s.target || s.target.hp <= 0) { if (Math.random() < 0.02) s.target = chooseTarget(s); }
  385.        const target = entities.find(e => e.id === s.target?.id); s.target = target;
  386.         if (target) {
  387.           const dx = target.x - s.x, dy = target.y - s.y, r = Math.hypot(dx, dy) + 0.01;
  388.           switch (s.archetype) {
  389.             case 'fighter': s.vx += (dx / r) * 0.02; s.vy += (dy / r) * 0.02; break;
  390.             case 'brawler': s.vx += (dx / r) * 0.03; s.vy += (dy / r) * 0.03; break;
  391.             case 'sniper': if (r < (s.range || 520) * 0.6) { s.vx -= (dx / r) * 0.02; s.vy -= (dy / r) * 0.02; } else { s.vx += (dx / r) * 0.01; s.vy += (dy / r) * 0.01; } break;
  392.            case 'kamikaze': s.vx += (dx / r) * 0.05; s.vy += (dy / r) * 0.05; break;
  393.            case 'support': {
  394.              const ally = entities.filter(e => e.kind === 'ship' && e.side === s.side && e.hp < e.maxHp).sort((a, b) => a.hp - b.hp)[0];
  395.               if (ally) { const adx = ally.x - s.x, ady = ally.y - s.y, ar = Math.hypot(adx, ady) + 0.01; s.vx += (adx / ar) * 0.015; s.vy += (ady / ar) * 0.015; if (ar < s.range * 0.8 && (performance.now() - s.lastShot > (s.fireRate * 1000))) { ally.hp = Math.min(ally.maxHp, ally.hp - s.damage); s.lastShot = performance.now(); } }
  396.               else { s.vx += rand(0.0006); s.vy += rand(0.0006); }
  397.             } break;
  398.             default: s.vx += rand(0.0005); s.vy += rand(0.0005);
  399.           }
  400.         } else { s.vx += rand(0.0006); s.vy += rand(0.0006); }
  401.       }
  402.  
  403.       if (s.weapon !== 'ram' && s.weapon !== 'repair') {
  404.        const now = performance.now();
  405.         if (s.target && (now - s.lastShot) > ((s.fireRate || 0.5) * 1000)) {
  406.          const dx = s.target.x - s.x, dy = s.target.y - s.y, r = Math.hypot(dx, dy);
  407.           if (r < (s.range || 300)) {
  408.            const lead = r / (s.weapon === 'laser' ? 15 : 8); const tx = s.target.x + (s.target.vx || 0) * lead, ty = s.target.y + (s.target.vy || 0) * lead;
  409.            const dirx = tx - s.x, diry = ty - s.y, magd = Math.hypot(dirx, diry) + 0.001; const speed = s.weapon === 'laser' ? 15 : 8.0;
  410.            spawnProjectile(s.x + (dirx / magd) * s.size, s.y + (diry / magd) * s.size, (dirx / magd) * speed + (s.vx || 0), (diry / magd) * speed + (s.vy || 0), s, s.weapon, 2.5, s.damage || 18);
  411.            s.lastShot = now;
  412.          }
  413.        }
  414.      }
  415.  
  416.      if (s.subtype === 'mothership') {
  417.        s.spawnTimer = (s.spawnTimer || 0) + dt;
  418.        if (s.spawnTimer > (s.spawnRate || 3.0)) { s.spawnTimer = 0; const choice = ['fighter', 'brawler', 'sniper'][Math.floor(Math.random() * 3)]; spawnActions[choice](s.x + rand(30), s.y + rand(30), s.side); }
  419.       }
  420.  
  421.       s.x += (s.vx || 0) * dt * 60; s.y += (s.vy || 0) * dt * 60;
  422.       if (s.x < -400) s.x = W() + 400; if (s.x > W() + 400) s.x = -400; if (s.y < -400) s.y = H() + 400; if (s.y > H() + 400) s.y = -400;
  423.       if (s.hp <= 0) destroyShip(s);
  424.    }
  425.  
  426.    for (let i = entities.length - 1; i >= 0; i--) {
  427.       const p = entities[i]; if (p?.kind !== 'proj') continue;
  428.       p.x += (p.vx || 0) * dt * 60; p.y += (p.vy || 0) * dt * 60; p.life -= dt; if (p.life <= 0) { removeEntityByIndex(i); continue; }
  429.      const possible = spatialQueryRegion(p.x, p.y, 80);
  430.      for (const hit of possible) { if (!hit || hit.kind !== 'ship' || hit.side === p.ownerSide) continue; const rr = (hit.size || 8) + (p.size || 2), dx = hit.x - p.x, dy = hit.y - p.y; if (dx * dx + dy * dy < rr * rr) { hit.hp -= p.damage || 18; mkFX('hit', p.x, p.y); removeEntityByIndex(i); if (hit.hp <= 0) destroyShip(hit); break; } }
  431.    }
  432.  
  433.    for (const s of ships) {
  434.      const neighbors = spatialQueryRegion(s.x, s.y, s.size + 80);
  435.      for (const b of neighbors) { if (!b || b.kind !== 'ship' || b.id <= s.id) continue; const dx = b.x - s.x, dy = b.y - s.y, r = (s.size || 8) + (b.size || 8); if (dx * dx + dy * dy < r * r) { const impact = Math.min(50, Math.hypot(b.vx - s.vx, b.vy - s.vy) * 2); s.hp -= impact; b.hp -= impact; const mag = Math.hypot(dx, dy) || 1, nx = dx / mag, ny = dy / mag; s.vx -= nx * 0.5; s.vy -= ny * 0.5; b.vx += nx * 0.5; b.vy += ny * 0.5; mkFX('spark', (s.x + b.x) / 2, (s.y + b.y) / 2, '#f88'); if (s.hp <= 0) destroyShip(s); if (b.hp <= 0) destroyShip(b); } }
  436.    }
  437.  
  438.    for (let i = entities.length - 1; i >= 0; i--) { const e = entities[i]; if (e?.kind === 'fx') { e.t = (e.t || 0) + dt; if (e.t > e.life) removeEntityByIndex(i); } }
  439.   }
  440.  
  441.   function drawShipInfo(ship) {
  442.     const panel = document.getElementById('shipPanel'), shipInfo = document.getElementById('shipInfo'), crewList = document.getElementById('crewList'), crewCount = document.getElementById('crewCount');
  443.     if (!ship) { panel.style.display = 'none'; return; }
  444.     shipInfo.innerHTML = `<h3>${ship.name || 'Unnamed Ship'}</h3><p>Type: ${ship.archetype || 'Unknown'}</p><p>Faction: ${factions[ship.side]?.name || 'Neutral'}</p><p>HP: ${Math.floor(ship.hp)}/${ship.maxHp}</p>${ship.shield ? `<p>Shield: ${Math.floor(ship.shield)}/${ship.maxShield}</p>` : ''}${ship.weapons?.length ? `<p>Weapons: ${ship.weapons.map(w => w.type).join(', ')}</p>` : ''}${ship.modules?.length ? `<p>Modules: ${ship.modules.join(', ')}</p>` : ''}`;
  445.     if (ship.crew?.length) {
  446.       crewCount.textContent = `(${ship.crew.length})`; crewList.innerHTML = '';
  447.       ship.crew.forEach(member => { const el = document.createElement('div'); el.className = 'crew-member'; el.innerHTML = `<span class="crew-role">${member.name} (${member.role})</span><span class="crew-stats">Skill: ${member.skill} | Morale: ${member.morale}</span>`; crewList.appendChild(el); });
  448.     } else { crewList.innerHTML = '<p>No crew</p>'; }
  449.     panel.style.display = 'block';
  450.   }
  451.  
  452.   function drawMissionPanel() {
  453.     const panel = document.getElementById('missionPanel'), missionList = document.getElementById('missionList'); missionList.innerHTML = '';
  454.     missions.forEach(mission => {
  455.       const card = document.createElement('div'); card.className = 'mission-card';
  456.       const rewards = Object.entries(mission.reward).map(([key, value]) => `${value} ${key}`).join(', ');
  457.       card.innerHTML = `<h3>${mission.name}</h3><p>${mission.desc}</p><p>Faction: ${factions[mission.faction]?.name || 'Neutral'}</p><p>Reward: ${rewards}</p><p>Difficulty: ${'β˜…'.repeat(mission.difficulty)}</p>${mission.accepted ? `<progress value="${mission.progress * 100}" max="100"></progress><p>Time remaining: ${Math.ceil(mission.timeLimit * (1 - mission.progress))}s</p>` : `<button class="accept-mission" data-id="${mission.id}">Accept Mission</button>`}`;
  458.       missionList.appendChild(card);
  459.     });
  460.     panel.style.display = 'block';
  461.   }
  462.  
  463.   function drawStatsPanel() {
  464.     const grid = document.getElementById('statsGrid');
  465.     const playerFaction = factions[TEAL];
  466.     if (!grid || !playerFaction) return;
  467.    
  468.     let html = '';
  469.     for (const resource in playerFaction.resources) {
  470.         html += `<div class="label">${resource.charAt(0).toUpperCase() + resource.slice(1)}</div><div class="value">${playerFaction.resources[resource]}</div>`;
  471.     }
  472.     html += `<div class="label" style="margin-top: 8px; grid-column: 1 / -1; color: var(--accent);">Reputation</div>`;
  473.     html += `<div class="label">TEAL</div><div class="value">${playerFaction.reputation}</div>`;
  474.     html += `<div class="label">RED</div><div class="value">${factions[RED].reputation}</div>`;
  475.    
  476.     grid.innerHTML = html;
  477.   }
  478.  
  479.   function draw() {
  480.     ctx.fillStyle = '#010214'; ctx.fillRect(0, 0, W(), H());
  481.     ctx.fillStyle = 'rgba(255,255,255,0.03)'; for (let i = 0; i < 120; i++) { const x = (i * 123.7 + performance.now() * 0.001) % W(), y = (i * 221.3) % H(); ctx.fillRect(x, y, 1, 1); }
  482.    ctx.save();
  483.    if (followId) { const f = entities.find(e => e.id === followId); if (f) { camera.targetX = W() / 2 - f.x; camera.targetY = H() / 2 - f.y; } }
  484.     camera.x += (camera.targetX - camera.x) * camera.lerp; camera.y += (camera.targetY - camera.y) * camera.lerp;
  485.     ctx.translate(camera.x, camera.y);
  486.  
  487.     missions.forEach(mission => {
  488.       if (mission.accepted) {
  489.         ctx.beginPath(); ctx.arc(mission.location.x, mission.location.y, 20, 0, TAU); ctx.fillStyle = hexToRGBA(factions[mission.faction]?.color || '#fff', 0.3); ctx.fill();
  490.         ctx.strokeStyle = hexToRGBA(factions[mission.faction]?.color || '#fff', 0.8); ctx.lineWidth = 2; ctx.stroke();
  491.         ctx.beginPath(); ctx.arc(mission.location.x, mission.location.y, 20, -Math.PI/2, -Math.PI/2 + TAU * mission.progress); ctx.strokeStyle = hexToRGBA('#6ee7b7', 0.8); ctx.lineWidth = 3; ctx.stroke();
  492.       }
  493.     });
  494.  
  495.     for (const e of entities) {
  496.       switch (e.kind) {
  497.         case 'celestial': {
  498.           const r = Math.max(2, e.size);
  499.           if (e.subtype === 'star') { const g = ctx.createRadialGradient(e.x, e.y, 0, e.x, e.y, r * 7); g.addColorStop(0, hexToRGBA(e.color || '#ffd166', 0.55)); g.addColorStop(1, hexToRGBA(e.color || '#ffd166', 0)); ctx.fillStyle = g; ctx.beginPath(); ctx.arc(e.x, e.y, r * 7, 0, TAU); ctx.fill(); }
  500.           ctx.beginPath(); ctx.fillStyle = e.color || '#88c'; ctx.arc(e.x, e.y, r, 0, TAU); ctx.fill();
  501.           if (e.subtype === 'planet' || e.subtype === 'star') { ctx.fillStyle = '#fff'; ctx.font = '10px Inter'; ctx.textAlign = 'center'; ctx.fillText(e.name || 'Unknown', e.x, e.y + r + 15); }
  502.         } break;
  503.         case 'ship': case 'structure': {
  504.           ctx.save(); ctx.translate(e.x, e.y);
  505.           const ang = Math.atan2(e.vy || 0, e.vx || 0); ctx.rotate(ang + Math.PI / 2);
  506.           const col = factions[e.side]?.color || '#999';
  507.           if (e.kind === 'ship') { ctx.beginPath(); ctx.moveTo(0, -e.size); ctx.lineTo(-e.size * 0.7, e.size); ctx.lineTo(e.size * 0.7, e.size); ctx.closePath(); ctx.fillStyle = col; ctx.fill(); }
  508.           else {
  509.             if (e.subtype === 'rebelShieldGen') { ctx.beginPath(); ctx.arc(0, 0, e.size, 0, TAU); ctx.strokeStyle = e.color || '#e74c3c'; ctx.lineWidth = 2; ctx.stroke(); }
  510.             else if (e.subtype === 'capitalCannon') { ctx.fillStyle = e.color || '#d33'; ctx.fillRect(-e.size / 2, -e.size / 2, e.size, e.size); }
  511.             else if (e.subtype === 'tradersDen' || e.subtype === 'enclaveSatellite') { ctx.beginPath(); ctx.arc(0, 0, e.size, 0, TAU); ctx.fillStyle = e.color || '#bdc3c7'; ctx.fill(); }
  512.             else { ctx.fillStyle = col; ctx.fillRect(-e.size / 2, -e.size / 2, e.size, e.size); }
  513.           }
  514.           if (e.hp < e.maxHp) { const hW = e.size * 1.5; ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(-hW / 2, e.size + 6, hW, 5); ctx.fillStyle = '#4caf50'; ctx.fillRect(-hW / 2, e.size + 6, hW * Math.max(0, (e.hp / e.maxHp)), 5); }
  515.          ctx.restore();
  516.          if (selectedIds.has(e.id) && e.name) { ctx.fillStyle = '#fff'; ctx.font = '10px Inter'; ctx.textAlign = 'center'; ctx.fillText(e.name, e.x, e.y - e.size - 10); }
  517.        } break;
  518.        case 'proj': { ctx.fillStyle = e.subtype === 'laser' ? '#9ef' : '#ffc'; ctx.beginPath(); ctx.arc(e.x, e.y, e.size || 2, 0, TAU); ctx.fill(); } break;
  519.        case 'fx': {
  520.          const p = (e.t / e.life) || 0;
  521.          if (e.subtype === 'bigExpl') { const r = (p * 36 + 10); ctx.fillStyle = `rgba(255,150,50,${1-p})`; ctx.beginPath(); ctx.arc(e.x, e.y, r, 0, TAU); ctx.fill(); }
  522.          else if (e.subtype === 'hit') { ctx.fillStyle = `rgba(255,255,255,${1-p})`; ctx.beginPath(); ctx.arc(e.x, e.y, 3+p*8, 0, TAU); ctx.fill(); }
  523.          else if (e.subtype === 'spark') { ctx.fillStyle = e.color || 'rgba(255,200,50,0.9)'; ctx.beginPath(); ctx.arc(e.x, e.y, 1+p*6, 0, TAU); ctx.fill(); }
  524.        } break;
  525.      }
  526.    }
  527.  
  528.    for (const id of selectedIds) {
  529.      const s = entities.find(e => e.id === id);
  530.       if (s) { ctx.strokeStyle = '#fff'; ctx.lineWidth = 1 / DPR; ctx.beginPath(); ctx.arc(s.x, s.y, (s.size || 8) + 6, 0, TAU); ctx.stroke(); ctx.beginPath(); ctx.arc(s.x, s.y, 2, 0, TAU); ctx.fillStyle = '#fff'; ctx.fill(); }
  531.       else { selectedIds.delete(id); }
  532.     }
  533.     ctx.restore();
  534.    
  535.     if (isDragging && selectionBox) {
  536.        ctx.save();
  537.         ctx.setTransform(DPR, 0, 0, DPR, 0, 0); // Use screen coordinates for selection box
  538.         ctx.strokeStyle = '#7ef'; ctx.lineWidth = 1; ctx.setLineDash([5, 5]);
  539.         ctx.strokeRect(
  540.             parseFloat(selectionBox.style.left),
  541.             parseFloat(selectionBox.style.top),
  542.             parseFloat(selectionBox.style.width),
  543.             parseFloat(selectionBox.style.height)
  544.         );
  545.         ctx.setLineDash([]);
  546.         ctx.restore();
  547.     }
  548.     document.getElementById('debug').textContent = `Entities: ${entities.length} β€” Projectiles: ${projCount} β€” FPS: ${fps} β€” Selected: ${selectedIds.size}`;
  549.     drawStatsPanel();
  550.   }
  551.  
  552.   function loop(time) {
  553.     const dt = (time - lastTime) / 1000; lastTime = time; if (running) step(dt); draw(); frameCount++;
  554.     if (time > lastFpsTime + CFG.FPS_INTERVAL) { fps = Math.round(frameCount / ((time - lastFpsTime) / 1000)); lastFpsTime = time; frameCount = 0; }
  555.     requestAnimationFrame(loop);
  556.   }
  557.  
  558.   function screenToWorld(screenX, screenY) { return { x: screenX - camera.x, y: screenY - camera.y }; }
  559.  
  560.   function handleTerminalCommand(cmd) {
  561.     cmd = cmd.trim(); if (!cmd) return;
  562.     terminalHistory.push(cmd); terminalHistoryIndex = terminalHistory.length;
  563.     const [command, ...args] = cmd.toLowerCase().split(/\s+/); const center = screenToWorld(W() / 2, H() / 2);
  564.     const playerFaction = factions[TEAL];
  565.     switch (command) {
  566.       case 'spawn': if (args[0] && spawnActions[args[0]]) { const count = parseInt(args[1]) || 1; const side = args[2] === 'teal' ? TEAL : RED; for (let i = 0; i < count; i++) { spawnActions[args[0]](center.x + rand(120), center.y + rand(120), side); } } else { console.log('Available spawn types: ' + Object.keys(spawnActions).join(', ')); } break;
  567.      case 'clear': entities = []; projCount = 0; nextId = 1; selectedIds.clear(); initFactions(); poolInit(); initGame(); break;
  568.      case 'stats': console.log(`Entities: ${entities.length}, Projectiles: ${projCount}, Selected Ships: ${selectedIds.size}`); break;
  569.      case 'explode': explode(center.x, center.y, parseFloat(args[0]) || 120, parseFloat(args[1]) || 500); break;
  570.      case 'teleport': if (selectedIds.size) { const x = parseFloat(args[0]) || center.x, y = parseFloat(args[1]) || center.y; for (const id of selectedIds) { const s = entities.find(e => e.id === id); if (s) { s.x = x + rand(20); s.y = y + rand(20); } } } break;
  571.       case 'mission': if (args[0] === 'list') { missions.forEach(m => console.log(`${m.name} - ${m.desc} (${m.reward.credits} credits)`)); } else if (args[0] === 'generate') { generateMissions(); console.log('Generated new missions'); } break;
  572.       case 'crew': if (selectedIds.size) { const ship = entities.find(e => e.id === [...selectedIds][0]); if (ship?.crew) { console.log(`${ship.crew.length} crew members:`); ship.crew.forEach(m => console.log(`${m.name} - ${m.role} (Skill: ${m.skill}, Morale: ${m.morale})`)); } } break;
  573.       case 'add':
  574.         if (args[0] && playerFaction.resources.hasOwnProperty(args[0])) {
  575.            playerFaction.resources[args[0]] += parseInt(args[1]) || 1;
  576.             drawStatsPanel();
  577.         } else { console.log('Invalid resource. Available: ' + Object.keys(playerFaction.resources).join(', ')) }
  578.         break;
  579.       default: console.log('Unknown command. Try: spawn, clear, stats, explode, teleport, mission, crew, add <resource> <amount>');
  580.     }
  581.   }
  582.  
  583.   function updateTerminalSuggestions(input) {
  584.     const suggestions = document.getElementById('terminal-suggestions'); suggestions.innerHTML = '';
  585.     const commands = [
  586.       { cmd: 'spawn <type> [count] [side]', desc: 'Spawn entities' }, { cmd: 'clear', desc: 'Clear all entities' }, { cmd: 'stats', desc: 'Show game statistics' },
  587.       { cmd: 'explode [radius] [damage]', desc: 'Create explosion' }, { cmd: 'teleport [x] [y]', desc: 'Teleport selected ships' },
  588.       { cmd: 'mission list', desc: 'List available missions' }, { cmd: 'mission generate', desc: 'Generate new missions' }, { cmd: 'crew', desc: 'Show crew info' },
  589.       { cmd: 'add <resource> <amount>', desc: 'Add resources to player faction' }
  590.     ];
  591.    
  592.     const filtered = input ? commands.filter(c => c.cmd.toLowerCase().startsWith(input.toLowerCase())) : commands;
  593.    
  594.     if (filtered.length) { filtered.forEach(cmd => { const el = document.createElement('div'); el.textContent = `${cmd.cmd} - ${cmd.desc}`; el.onclick = () => { document.getElementById('terminalInput').value = cmd.cmd.split(' ')[0] + ' '; suggestions.style.display = 'none'; document.getElementById('terminalInput').focus(); }; suggestions.appendChild(el); }); suggestions.style.display = 'block'; } else { suggestions.style.display = 'none'; }
  595.   }
  596.  
  597.   class PieMenu {
  598.     constructor(items) { this.items = items; this.root = null; this.center = { x: 0, y: 0 }; }
  599.     show(clientX, clientY) {
  600.       this.hide(); const cx = Math.round(clientX), cy = Math.round(clientY); this.center = { x: cx, y: cy };
  601.       const root = document.createElement('div'); root.className = 'pie-container'; root.style.transform = `translate(${cx}px, ${cy}px)`;
  602.       const wrap = document.createElement('div'); wrap.className = 'pie-wrap'; root.appendChild(wrap);
  603.       const N = this.items.length; const step = TAU / N; const radius = 100; const startOffset = Math.PI / 2;
  604.       for (let i = 0; i < N; i++) {
  605.        const item = this.items[i]; const slice = document.createElement('div'); slice.className = 'pie-slice'; const ang = i * step + startOffset;
  606.        const tx = Math.round(Math.cos(ang) * radius); const ty = Math.round(Math.sin(ang) * radius);
  607.        slice.style.transform = `translate(${tx}px, ${ty}px)`; slice.style.background = hexToRGBA(item.color || '#07202a', 0.88);
  608.        slice.innerHTML = `<div class="label">${item.label}</div><div class="pie-divider"></div>`;
  609.         const onDown = (e) => { e.stopPropagation(); const world = screenToWorld(cx, cy); try { item.action(world.x, world.y); } finally { this.hide(); } };
  610.         slice.addEventListener('mousedown', onDown); slice.addEventListener('touchstart', onDown, { passive: true });
  611.         wrap.appendChild(slice);
  612.       }
  613.       document.body.appendChild(root); this.root = root;
  614.       setTimeout(() => document.addEventListener('mousedown', this.boundHideOnce || (this.boundHideOnce = this.hide.bind(this)), { once: true }), 0);
  615.       setTimeout(() => document.addEventListener('touchstart', this.boundHideOnceTouch || (this.boundHideOnceTouch = this.hide.bind(this)), { once: true, passive: true }), 0);
  616.     }
  617.     hide() { if (this.root) { this.root.remove(); this.root = null; } }
  618.   }
  619.  
  620.   const playerPie = new PieMenu([ { label: 'Fighter', color: factions[TEAL].color, action: (x, y) => spawnActions.fighter(x, y, TEAL) }, { label: 'Brawler', color: factions[TEAL].color, action: (x, y) => spawnActions.brawler(x, y, TEAL) }, { label: 'Sniper', color: factions[TEAL].color, action: (x, y) => spawnActions.sniper(x, y, TEAL) }, { label: 'Support', color: factions[TEAL].color, action: (x, y) => spawnActions.support(x, y, TEAL) }, { label: 'Mothership', color: factions[TEAL].color, action: (x, y) => spawnActions.playerMothership(x, y) }]);
  621.   const enemyPie = new PieMenu([ { label: 'Enemy Fighter', color: factions[RED].color, action: (x, y) => spawnActions.fighter(x, y, RED) }, { label: 'Enemy Brawler', color: factions[RED].color, action: (x, y) => spawnActions.brawler(x, y, RED) }, { label: 'Enemy Sniper', color: factions[RED].color, action: (x, y) => spawnActions.sniper(x, y, RED) }, { label: 'Enemy Mother', color: factions[RED].color, action: (x, y) => spawnActions.enemyMothership(x, y) }]);
  622.   const astroPie = new PieMenu([ { label: 'Place Star', color: '#ffd166', action: (x, y) => spawnActions.star(x, y) }, { label: 'Place Planet', color: '#7fb', action: (x, y) => spawnActions.planet(x, y) }, { label: 'Asteroid Field', color: '#b7a', action: (x, y) => spawnActions.asteroids(x, y) }]);
  623.   const secretPie = new PieMenu([ { label: 'Capital Cannons', color: '#ff6b6b', action: (x, y) => spawnActions.capitalCannon(x, y, RED) }, { label: 'Rebel Shield Gen', color: '#e74c3c', action: (x, y) => spawnActions.rebelShieldGen(x, y) }, { label: "Trader's Den", color: '#f1c40f', action: (x, y) => spawnActions.tradersDen(x, y) }, { label: 'Enclave Satellite', color: '#bdc3c7', action: (x, y) => spawnActions.enclaveSatellite(x, y) }, { label: 'Earth Planet', color: '#2c3e50', action: (x, y) => spawnActions.earthPlanet(x, y) }]);
  624.   const zMenu = new PieMenu([ { label: 'Attack Nearest', color: '#ff6b6b', action: (x, y) => { for (const id of selectedIds) { const s = entities.find(e => e.id === id); if (s && s.playerControl) { s.target = chooseTarget(s); if (s.target) { const dx = s.target.x - s.x; const dy = s.target.y - s.y; const dist = Math.hypot(dx, dy); if (dist < (s.range || 300)) { s.vx += (dx / dist) * 0.1; s.vy += (dy / dist) * 0.1; } } } } } }, { label: 'Move To', color: '#6ee7b7', action: (x, y) => { for (const id of selectedIds) { const s = entities.find(e => e.id === id); if (s && s.playerControl) { s.target = null; const dx = x - s.x; const dy = y - s.y; const dist = Math.hypot(dx, dy); if (dist > 10) { s.vx += (dx / dist) * 0.05; s.vy += (dy / dist) * 0.05; } } } } }, { label: 'Hold Position', color: '#a78bfa', action: () => { for (const id of selectedIds) { const s = entities.find(e => e.id === id); if (s && s.playerControl) { s.vx = 0; s.vy = 0; s.target = null; } } } }, { label: 'Self-Destruct', color: '#e74c3c', action: () => { for (const id of selectedIds) { const s = entities.find(e => e.id === id); if (s && s.playerControl) destroyShip(s); } } }, { label: 'Repair Fleet', color: '#2ecc71', action: () => { for (const id of selectedIds) { const s = entities.find(e => e.id === id); if (s && s.playerControl) { s.hp = Math.min(s.maxHp, s.hp + s.maxHp * 0.25); if (s.shield !== undefined) { s.shield = Math.min(s.maxShield, s.shield + s.maxShield * 0.5); } mkFX('spark', s.x, s.y, '#2ecc71'); } } } }]);
  625.  
  626.   const keys = new Set();
  627.   c.addEventListener('mousemove', (e) => {
  628.     mouse.screenX = e.clientX; mouse.screenY = e.clientY;
  629.     if (isDragging && selectionBox) {
  630.      selectionBox.style.width = Math.abs(e.clientX - mouse.startX) + 'px'; selectionBox.style.height = Math.abs(e.clientY - mouse.startY) + 'px';
  631.       selectionBox.style.left = Math.min(e.clientX, mouse.startX) + 'px'; selectionBox.style.top = Math.min(e.clientY, mouse.startY) + 'px';
  632.     }
  633.   });
  634.   c.addEventListener('mousedown', (e) => {
  635.     if (e.button === 2) { e.preventDefault(); if (keys.has('z')) { zMenu.show(e.clientX, e.clientY); } else if (e.shiftKey) { secretPie.show(e.clientX, e.clientY); } else if (e.ctrlKey || e.metaKey) { enemyPie.show(e.clientX, e.clientY); } else if (e.altKey) { astroPie.show(e.clientX, e.clientY); } else { playerPie.show(e.clientX, e.clientY); } return; }
  636.     if (e.button === 0) {
  637.       mouse.down = true; mouse.startX = e.clientX; mouse.startY = e.clientY;
  638.       if (!e.ctrlKey && !e.metaKey) selectedIds.clear();
  639.       const world = screenToWorld(e.clientX, e.clientY);
  640.       const clicked = spatialQueryRegion(world.x, world.y, 60).slice().reverse().find(en => en.kind === 'ship' && Math.hypot(en.x - world.x, en.y - world.y) < en.size);
  641.       if (clicked) { selectedIds.add(clicked.id); drawShipInfo(clicked); }
  642.       else { isDragging = true; selectionBox = document.createElement('div'); selectionBox.className = 'selection-box'; selectionBox.style.left = e.clientX + 'px'; selectionBox.style.top = e.clientY + 'px'; selectionBox.style.width = '0px'; selectionBox.style.height = '0px'; document.body.appendChild(selectionBox); }
  643.     }
  644.   });
  645.   c.addEventListener('mouseup', (e) => {
  646.     if (e.button === 0 && isDragging) {
  647.      isDragging = false;
  648.       if (selectionBox) {
  649.         const rect = selectionBox.getBoundingClientRect();
  650.         const worldStart = screenToWorld(rect.left, rect.top);
  651.         const worldEnd = screenToWorld(rect.right, rect.bottom);
  652.         const ships = entities.filter(s => s.kind === 'ship');
  653.         for (const s of ships) {
  654.           if (s.x > worldStart.x && s.x < worldEnd.x && s.y > worldStart.y && s.y < worldEnd.y) {
  655.            selectedIds.add(s.id);
  656.           }
  657.         }
  658.         if (selectedIds.size) { const firstShip = entities.find(e => e.id === [...selectedIds][0]); drawShipInfo(firstShip); }
  659.         selectionBox.remove(); selectionBox = null;
  660.       }
  661.     }
  662.     mouse.down = false;
  663.   });
  664.   c.addEventListener('contextmenu', (e) => e.preventDefault());
  665.  
  666.   document.addEventListener('keydown', (e) => {
  667.     const k = e.key.toLowerCase(); keys.add(k);
  668.     if (document.activeElement === terminalInput) return;
  669.     if (k === 'p') { running = !running; document.getElementById('btnPause').textContent = running ? 'Pause (P)' : 'Resume (P)'; }
  670.     if (k === 'm') document.getElementById('btnMegaToggle').click();
  671.     if (k === '`' || k === '~') { e.preventDefault(); const t = document.getElementById('terminal'); t.style.display = t.style.display === 'block' ? 'none' : 'block'; if (t.style.display === 'block') { terminalInput.focus(); updateTerminalSuggestions(''); } }
  672.     if (k === 'f') { const ships = entities.filter(s => s.kind === 'ship'); if (ships.length) { const idx = ships.findIndex(s => s.id === followId); followId = ships[(idx + 1) % ships.length].id; } }
  673.     if (k === 'j') { drawMissionPanel(); }
  674.     if (k === 'i') { if (selectedIds.size) { const ship = entities.find(e => e.id === [...selectedIds][0]); drawShipInfo(ship); } else { document.getElementById('shipPanel').style.display = 'none'; } }
  675.   });
  676.   document.addEventListener('keyup', (e) => { keys.delete(e.key.toLowerCase()); });
  677.  
  678.   const terminalInput = document.getElementById('terminalInput');
  679.   terminalInput.addEventListener('keydown', (e) => {
  680.     e.stopPropagation();
  681.     if (e.key === 'Enter') { handleTerminalCommand(terminalInput.value); terminalInput.value = ''; document.getElementById('terminal-suggestions').style.display = 'none'; }
  682.     else if (e.key === 'Escape') { document.getElementById('terminal').style.display = 'none'; }
  683.     else if (e.key === 'ArrowUp') { if (terminalHistoryIndex > 0) { terminalHistoryIndex--; terminalInput.value = terminalHistory[terminalHistoryIndex] || ''; } }
  684.     else if (e.key === 'ArrowDown') { if (terminalHistoryIndex < terminalHistory.length - 1) { terminalHistoryIndex++; terminalInput.value = terminalHistory[terminalHistoryIndex] || ''; } else { terminalHistoryIndex = terminalHistory.length; terminalInput.value = ''; } }
  685.    else { setTimeout(() => updateTerminalSuggestions(terminalInput.value), 0); }
  686.   });
  687.   terminalInput.addEventListener('focus', () => updateTerminalSuggestions(terminalInput.value));
  688.   terminalInput.addEventListener('blur', () => { setTimeout(() => document.getElementById('terminal-suggestions').style.display = 'none', 150); });
  689.  
  690.   document.getElementById('missionList').addEventListener('click', (e) => { if (e.target.classList.contains('accept-mission')) { const missionId = parseInt(e.target.dataset.id); const mission = missions.find(m => m.id === missionId); if (mission) { mission.accepted = true; drawMissionPanel(); } } });
  691.   document.getElementById('closeMissions').addEventListener('click', () => { document.getElementById('missionPanel').style.display = 'none'; });
  692.  
  693.   setInterval(() => {
  694.     if (!running || !selectedIds.size) return;
  695.     const selectedShips = entities.filter(e => selectedIds.has(e.id) && e.playerControl);
  696.     if (!selectedShips.length) return;
  697.     const now = performance.now();
  698.     for (const s of selectedShips) {
  699.       if (keys.has('a')) s.vx -= 0.08; if (keys.has('d')) s.vx += 0.08;
  700.       if (keys.has('w')) s.vy -= 0.08; if (keys.has('s')) s.vy += 0.08;
  701.       if (keys.has(' ')) {
  702.         if (!s.target || s.target.hp <= 0) s.target = chooseTarget(s);
  703.        if (s.target && (!s.lastShot || (now - s.lastShot) > ((s.fireRate || 0.22) * 1000))) {
  704.           const dx = s.target.x - s.x; const dy = s.target.y - s.y; const dist = Math.hypot(dx, dy);
  705.           if (dist < (s.range || 300)) { const dir = Math.atan2(dy, dx); const speed = 12; spawnProjectile(s.x, s.y, Math.cos(dir) * speed + s.vx, Math.sin(dir) * speed + s.vy, s, 'bullet', 2.5, 35); s.lastShot = now; }
  706.        }
  707.      }
  708.    }
  709.  }, 16);
  710.  
  711.  document.getElementById('btnPause').onclick = () => { running = !running; document.getElementById('btnPause').textContent = running ? 'Pause (P)' : 'Resume (P)'; };
  712.   document.getElementById('btnCenter').onclick = () => { followId = null; camera.targetX = 0; camera.targetY = 0; };
  713.   document.getElementById('btnClear').onclick = () => { entities = []; projCount = 0; nextId = 1; selectedIds.clear(); initFactions(); poolInit(); initGame(); document.getElementById('shipPanel').style.display = 'none'; };
  714.   document.getElementById('btnMegaToggle').onclick = () => { const m = document.getElementById('mega'); m.style.display = m.style.display === 'block' ? 'none' : 'block'; };
  715.   document.getElementById('btnTerminal').onclick = () => { const t = document.getElementById('terminal'); t.style.display = t.style.display === 'block' ? 'none' : 'block'; if (t.style.display === 'block') { terminalInput.focus(); updateTerminalSuggestions(''); } };
  716.   document.getElementById('btnMissions').onclick = () => { drawMissionPanel(); };
  717.   document.getElementById('btnShipPanel').onclick = () => { if (selectedIds.size) { const ship = entities.find(e => e.id === [...selectedIds][0]); drawShipInfo(ship); } else { document.getElementById('shipPanel').style.display = 'none'; } };
  718.   document.getElementById('mega').onclick = (e) => { if (e.target.classList.contains('spawn-btn')) { const action = e.target.dataset.action; if (spawnActions[action]) { const center = screenToWorld(W() / 2, H() / 2); spawnActions[action](center.x + rand(120), center.y + rand(120)); } } };
  719.  
  720.   function initGame() {
  721.     const center = screenToWorld(W() / 2, H() / 2);
  722.     spawnActions.star(center.x, center.y); spawnActions.playerMothership(center.x - 420, center.y);
  723.     spawnActions.enemyMothership(center.x + 420, center.y);
  724.     spawnActions.planet(center.x - 200, center.y - 150); spawnActions.planet(center.x + 250, center.y + 100);
  725.     spawnActions.lushPlanet(center.x + 180, center.y - 200);
  726.   }
  727.  
  728.   initGame();
  729.   requestAnimationFrame(loop);
  730.   window.__SPACEVERSE_V56 = { spawnActions, entities, spawnProjectile, removeEntityById, factions, missions, generateMissions, generateCrew };
  731. })();
  732. </script>
  733. </body>
  734. </html>
  735.  
Advertisement
Add Comment
Please, Sign In to add comment