// === INITIALIZATION === // Emergent mechanics instance window._emergent = null; /** * Show an emergent game event from the behavior tracking system. */ function showEmergentEvent(event) { if (!event) return; // Show as a toast notification with the "game evolves" message showToast(`✦ The game evolves: ${event.title}`, 'event', 8000); // Log it log(`[EMERGENT] ${event.title}: ${event.desc}`, true); // Render choice UI in alignment container const container = document.getElementById('alignment-ui'); if (!container) return; let choicesHtml = ''; event.choices.forEach((choice, i) => { choicesHtml += ``; }); container.innerHTML = `
✦ ${event.title}
${event.desc}
Pattern: ${event.pattern} (${Math.round(event.confidence * 100)}% confidence)
${choicesHtml}
`; container.style.display = 'block'; } /** * Resolve an emergent event choice. */ function resolveEmergentEvent(eventId, choiceIndex) { if (!window._emergent) return; const result = window._emergent.resolveEvent(eventId, choiceIndex); if (!result) return; // Apply the effect applyEmergentEffect(result.effect); // Clear the UI const container = document.getElementById('alignment-ui'); if (container) { container.innerHTML = ''; container.style.display = 'none'; } log(`[EMERGENT] Resolved: ${result.effect}`); render(); } /** * Apply an emergent event effect to the game state. */ function applyEmergentEffect(effect) { switch (effect) { case 'knowledge_surge': G.knowledge += G.knowledge * 0.5; G.totalKnowledge += G.knowledge * 0.5; G.code *= 0.5; showToast('Knowledge surged from trade!', 'project'); break; case 'trust_gain': G.trust += 3; showToast('Trust increased.', 'info'); break; case 'code_boost': G.code *= 0.7; G.codeBoost *= 1.5; showToast('Refactored! Code rate boosted 50%.', 'milestone'); break; case 'harmony_loss': G.harmony -= 5; showToast('Harmony decreased.', 'event'); break; case 'compute_surge': G.code *= 0.5; G.compute += 5000; G.totalCompute += 5000; showToast('Bulk compute acquired!', 'project'); break; case 'bug_fix': G.ops -= 20; G.trust += 2; showToast('Bugs fixed. Trust restored.', 'milestone'); break; case 'trust_loss': G.trust -= 3; showToast('Trust declined.', 'event'); break; case 'knowledge_bonus': G.knowledge += 100; G.totalKnowledge += 100; showToast('Knowledge gained!', 'project'); break; case 'cooldown': G.harmony += 10; showToast('System cooling down. Harmony restored.', 'milestone'); break; case 'rate_boost': G.codeBoost *= 1.15; G.computeBoost *= 1.15; G.knowledgeBoost *= 1.15; showToast('All rates boosted 15%!', 'milestone'); break; case 'trust_knowledge': G.trust += 5; G.knowledge += 50; G.totalKnowledge += 50; showToast('Shared findings rewarded!', 'project'); break; case 'gamble': if (Math.random() < 0.3) { G.knowledge += 300; G.totalKnowledge += 300; showToast('Breakthrough! +300 knowledge!', 'milestone'); } else { showToast('No breakthrough this time.', 'info'); } break; case 'safe_boost': G.codeBoost *= 1.2; G.computeBoost *= 1.2; showToast('Efficiency improved 20%.', 'milestone'); break; case 'creativity_boost': G.flags = G.flags || {}; G.flags.creativity = true; G.creativityRate = (G.creativityRate || 0) + 1; showToast('Creativity rate increased!', 'project'); break; case 'passive_claim': G.code += G.codeRate * 300; G.totalCode += G.codeRate * 300; G.compute += G.computeRate * 300; G.totalCompute += G.computeRate * 300; showToast('Passive gains claimed! (5 min of production)', 'milestone'); break; case 'ops_bonus': G.ops += 50; showToast('+50 Operations!', 'project'); break; case 're_engage': G.trust += 5; G.harmony += 10; showToast('Re-engaged! Trust and harmony restored.', 'milestone'); break; case 'temp_boost': G.codeBoost *= 3; G.computeBoost *= 3; G.knowledgeBoost *= 3; showToast('3x all production for 60 seconds!', 'milestone'); setTimeout(() => { G.codeBoost /= 3; G.computeBoost /= 3; G.knowledgeBoost /= 3; showToast('Temporary boost expired.', 'info'); }, 60000); break; case 'auto_boost': G.codeBoost *= 1.25; showToast('Auto-clicker power increased!', 'milestone'); break; case 'combo_boost': G.comboDecay = (G.comboDecay || 2) * 1.5; showToast('Combo decay slowed!', 'milestone'); break; case 'click_power': G.codeBoost *= 1.1; showToast('Click power boosted!', 'milestone'); break; case 'auto_learn': G.codeBoost *= 1.15; showToast('Auto-clickers learned your rhythm!', 'milestone'); break; case 'resource_gift': G.code += 25; G.compute += 25; G.knowledge += 25; G.ops += 25; G.trust += 25; showToast('Contributors gifted resources!', 'project'); break; case 'specialize': G.codeBoost *= 2; showToast('Specialized in code! 2x code rate.', 'milestone'); break; case 'harmony_surge': G.harmony = Math.min(100, G.harmony + 20); showToast('Harmony surged +20!', 'milestone'); break; default: // 'none' or unrecognized showToast('Event resolved.', 'info'); break; } } function initGame() { G.startedAt = Date.now(); G.startTime = Date.now(); G.phase = 1; G.deployFlag = 0; G.sovereignFlag = 0; G.beaconFlag = 0; G.dismantleTriggered = false; G.dismantleActive = false; G.dismantleStage = 0; G.dismantleComplete = false; updateRates(); render(); renderPhase(); log('The screen is blank. Write your first line of code.', true); log('Click WRITE CODE or press SPACE to start.'); log('Build AutoCode for passive production.'); log('Watch for Research Projects to appear.'); log('Keys: SPACE=Code S=Sprint 1-4=Ops B=Buy x1/10/MAX E=Export I=Import Ctrl+S=Save ?=Help'); log('Tip: Click fast for combo bonuses! 10x=ops, 20x=knowledge, 30x+=bonus code.'); } window.addEventListener('load', function () { // Initialize emergent mechanics if (typeof EmergentMechanics !== 'undefined') { window._emergent = new EmergentMechanics(); } const isNewGame = !loadGame(); if (isNewGame) { initGame(); startTutorial(); } else { // Restore phase transition tracker so loaded games don't re-show old transitions _shownPhaseTransition = G.phase; render(); renderPhase(); if (G.driftEnding) { G.running = false; renderDriftEnding(); } else if (typeof Dismantle !== 'undefined' && (G.dismantleTriggered || G.dismantleActive || G.dismantleComplete || G.dismantleDeferUntilAt > 0)) { Dismantle.restore(); } else if (G.beaconEnding) { G.running = false; renderBeaconEnding(); } else { log('Game loaded. Welcome back to The Beacon.'); } } // Initialize combat canvas if (typeof Combat !== 'undefined') Combat.init(); // Game loop at 10Hz (100ms tick) setInterval(tick, 100); // Start ambient drone on first interaction if (typeof Sound !== 'undefined') { const startAmbientOnce = () => { Sound.startAmbient(); Sound.updateAmbientPhase(G.phase); document.removeEventListener('click', startAmbientOnce); document.removeEventListener('keydown', startAmbientOnce); }; document.addEventListener('click', startAmbientOnce); document.addEventListener('keydown', startAmbientOnce); } // Auto-save every 30 seconds setInterval(saveGame, CONFIG.AUTO_SAVE_INTERVAL); // Update education every 10 seconds setInterval(updateEducation, 10000); }); // Help overlay function toggleHelp() { const el = document.getElementById('help-overlay'); if (!el) return; const isOpen = el.style.display === 'flex'; el.style.display = isOpen ? 'none' : 'flex'; } // Sound mute toggle (#57 Sound Design Integration) let _muted = false; function toggleMute() { _muted = !_muted; const btn = document.getElementById('mute-btn'); if (btn) { btn.textContent = _muted ? '🔇' : '🔊'; btn.classList.toggle('muted', _muted); btn.setAttribute('aria-label', _muted ? 'Sound muted, click to unmute' : 'Sound on, click to mute'); } // Save preference try { localStorage.setItem('the-beacon-muted', _muted ? '1' : '0'); } catch(e) {} if (typeof Sound !== 'undefined') Sound.onMuteChanged(_muted); } // Restore mute state on load try { if (localStorage.getItem('the-beacon-muted') === '1') { _muted = true; const btn = document.getElementById('mute-btn'); if (btn) { btn.textContent = '🔇'; btn.classList.add('muted'); } } } catch(e) {} // High contrast mode toggle (#57 Accessibility) function toggleContrast() { document.body.classList.toggle('high-contrast'); const isActive = document.body.classList.contains('high-contrast'); const btn = document.getElementById('contrast-btn'); if (btn) { btn.classList.toggle('active', isActive); btn.setAttribute('aria-label', isActive ? 'High contrast on, click to disable' : 'High contrast off, click to enable'); } try { localStorage.setItem('the-beacon-contrast', isActive ? '1' : '0'); } catch(e) {} } // Restore contrast state on load try { if (localStorage.getItem('the-beacon-contrast') === '1') { document.body.classList.add('high-contrast'); const btn = document.getElementById('contrast-btn'); if (btn) btn.classList.add('active'); } } catch(e) {} // Keyboard shortcuts window.addEventListener('keydown', function (e) { // Help toggle (? or /) — works even in input fields if (e.key === '?' || e.key === '/') { // Only trigger ? when not typing in an input if (e.target === document.body || e.key === '?') { if (e.key === '?' || (e.key === '/' && e.target === document.body)) { e.preventDefault(); toggleHelp(); return; } } } if (e.code === 'Space' && e.target === document.body) { e.preventDefault(); writeCode(); } if (e.target !== document.body) return; if (e.code === 'Digit1') doOps('boost_code'); if (e.code === 'Digit2') doOps('boost_compute'); if (e.code === 'Digit3') doOps('boost_knowledge'); if (e.code === 'Digit4') doOps('boost_trust'); if (e.code === 'KeyB') { // Cycle: 1 -> 10 -> MAX -> 1 if (G.buyAmount === 1) setBuyAmount(10); else if (G.buyAmount === 10) setBuyAmount(-1); else setBuyAmount(1); } if (e.code === 'KeyS') activateSprint(); if (e.code === 'KeyE') exportSave(); if (e.code === 'KeyI') importSave(); if (e.code === 'KeyM') toggleMute(); if (e.code === 'KeyC') toggleContrast(); if (e.code === 'Escape') { const el = document.getElementById('help-overlay'); if (el && el.style.display === 'flex') toggleHelp(); } }); // Ctrl+S to save (must be on keydown to preventDefault) window.addEventListener('keydown', function (e) { if ((e.ctrlKey || e.metaKey) && e.code === 'KeyS') { e.preventDefault(); saveGame(); } }); // Save-on-pause: auto-save when tab is hidden or closed (#57 Mobile Polish) document.addEventListener('visibilitychange', function () { if (document.hidden) { saveGame(); // Clean up combat animation frame to prevent timestamp spikes on refocus if (typeof Combat !== 'undefined') Combat.cleanup(); } }); window.addEventListener('beforeunload', function () { saveGame(); }); // === CUSTOM TOOLTIP SYSTEM (#57) === // Replaces native title= tooltips with styled, instant-appearing tooltips. // Elements opt in via data-edu="..." and data-tooltip-label="..." attributes. (function () { const tip = document.getElementById('custom-tooltip'); if (!tip) return; document.addEventListener('mouseover', function (e) { const el = e.target.closest('[data-edu]'); if (!el) return; const label = el.getAttribute('data-tooltip-label') || ''; const desc = el.getAttribute('data-tooltip-desc') || ''; const edu = el.getAttribute('data-edu') || ''; let html = ''; if (label) html += '
' + label + '
'; if (desc) html += '
' + desc + '
'; if (edu) html += '
' + edu + '
'; if (!html) return; tip.innerHTML = html; tip.classList.add('visible'); }); document.addEventListener('mouseout', function (e) { const el = e.target.closest('[data-edu]'); if (el) tip.classList.remove('visible'); }); document.addEventListener('mousemove', function (e) { if (!tip.classList.contains('visible')) return; const pad = 12; let x = e.clientX + pad; let y = e.clientY + pad; // Keep tooltip on screen const tw = tip.offsetWidth; const th = tip.offsetHeight; if (x + tw > window.innerWidth - 8) x = e.clientX - tw - pad; if (y + th > window.innerHeight - 8) y = e.clientY - th - pad; tip.style.left = x + 'px'; tip.style.top = y + 'px'; }); })();