🎮ArcadeLab

Faulty Coding: Nega 64

by FlashGalaxy17
1631 lines75.3 KB🛠️ Three.js (3D graphics)
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Faulty Coding: Nega 64</title>
    <!-- Import Retro Arcade Font -->
    <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
    <style>
        body { margin: 0; overflow: hidden; background: #000; font-family: 'Press Start 2P', monospace; user-select: none; }
        
        /* Retro GameCube/Pixel Filter Overlay */
        #gamecube-filter {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 300;
            background: 
                radial-gradient(circle, rgba(0,0,0,0) 45%, rgba(0,0,0,0.55) 85%),
                linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.22) 50%);
            background-size: 100% 4px;
        }

        /* Global UI Screens */
        .screen {
            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            background: #080205; z-index: 100;
            display: flex; flex-direction: column; align-items: center; justify-content: center;
            border: 8px solid #330011; box-sizing: border-box;
        }
        .hidden { display: none !important; }

        /* Main Title Screen */
        #title-screen h1 {
            color: #ff3355; font-size: 3rem; text-align: center;
            text-shadow: 0 0 20px #ff0033, 0 0 40px #550000;
            animation: glitchText 0.3s infinite;
            margin-bottom: 10px;
        }
        #title-screen h2 { color: #00ff41; font-size: 1rem; margin-bottom: 40px; text-shadow: 0 0 10px #00ff41; }
        .menu-options { display: flex; flex-direction: column; gap: 15px; align-items: center; }
        .menu-btn {
            background: #111; color: #fff; border: 3px solid #444; padding: 12px 25px;
            font-family: 'Press Start 2P', monospace; font-size: 0.9rem; cursor: pointer;
            transition: all 0.1s; text-shadow: 0 0 5px #fff;
        }
        .menu-btn.selected, .menu-btn:hover {
            background: #00ff41; color: #000; border-color: #fff;
            box-shadow: 0 0 15px #00ff41; transform: scale(1.05);
        }

        /* Rules / Settings Screen */
        .rules-container { display: flex; gap: 30px; margin: 30px 0; flex-wrap: wrap; justify-content: center; }
        .rule-box {
            background: #110508; border: 3px solid #ff3355; padding: 20px; text-align: center; width: 220px;
        }
        .rule-box h3 { color: #ffcc00; font-size: 0.8rem; margin-top: 0; }
        .rule-val { color: #00ff41; font-size: 1.2rem; margin: 15px 0; text-shadow: 0 0 8px #00ff41; }

        /* Metrics System Screen */
        .stats-box {
            background: #001104; border: 2px solid #00ff41; padding: 25px; width: 450px; font-size: 0.7rem; color: #fff; line-height: 1.8;
        }

        /* Character Select Screen */
        .select-grid { display: flex; gap: 15px; margin-top: 25px; flex-wrap: wrap; justify-content: center; max-width: 800px; }
        .char-card {
            width: 120px; height: 160px; border: 4px solid #444;
            background: #111; display: flex; flex-direction: column; align-items: center;
            justify-content: space-between; padding: 10px 5px; box-sizing: border-box;
        }
        .char-preview { 
            width: 70px; height: 70px; background: #000; border: 2px solid #222;
            display: flex; align-items: center; justify-content: center; font-size: 1.5rem;
        }
        .selectors-status { display: flex; gap: 30px; margin-top: 25px; width: 750px; justify-content: space-around; }
        .player-select-box {
            border: 3px solid #00ff41; padding: 15px; text-align: center; width: 330px;
            background: #001104; box-shadow: 0 0 15px rgba(0,255,65,0.3); font-size: 0.65rem;
        }
        .player-select-box.p2-box { border-color: #ffcc00; color: #ffcc00; background: #141100; box-shadow: 0 0 15px rgba(255,204,0,0.3); }

        /* Finisher Cinematic Overlay */
        #finisher-flash {
            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(255, 0, 0, 0); z-index: 0; pointer-events: none; opacity: 0;
            transition: opacity 0.1s ease-in-out;
            box-shadow: inset 0 0 150px #ff0000;
        }
        #finisher-flash.active { opacity: 1; background: rgba(255, 0, 0, 0.75); animation: flashPulse 0.1s infinite alternate; }

        @keyframes flashPulse {
            0% { background: rgba(255, 0, 0, 0.65); }
            100% { background: rgba(255, 50, 50, 0.95); }
        }

        /* Countdown 3-2-1-GO Overlay */
        #countdown-overlay {
            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            display: flex; align-items: center; justify-content: center;
            z-index: 150; pointer-events: none;
        }
        .countdown-text {
            font-size: 7rem; color: #ffcc00; text-shadow: 0 0 30px #ff0000, 0 0 60px #ff3355;
            animation: countPulse 0.8s ease-out infinite alternate;
        }
        @keyframes countPulse {
            0% { transform: scale(0.8); opacity: 0.5; }
            100% { transform: scale(1.1); opacity: 1; }
        }

        /* Game Over / Victory Overlay Layers */
        #game-overlay {
            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            display: flex; flex-direction: column; align-items: center; justify-content: center;
            background: rgba(0, 0, 0, 0.9); z-index: 200; pointer-events: none; opacity: 0; transition: opacity 0.4s;
        }
        #game-overlay.active { opacity: 1; pointer-events: auto; }
        
        .game-callout {
            font-size: 6rem; color: #ffcc00; text-shadow: 0 0 30px #ff0000, 0 0 60px #ff3355;
            animation: zoomPulse 0.3s infinite alternate;
        }
        
        #victory-container {
            display: none; flex-direction: column; align-items: center; justify-content: center;
        }
        #victory-container.show { display: flex; }
        
        .winner-callout {
            font-size: 2.2rem; color: #00ff41; text-shadow: 0 0 20px #00ff41;
            margin-bottom: 30px; text-align: center;
        }
        .final-stats-summary {
            color: #fff; font-size: 0.8rem; margin-bottom: 30px; text-align: center; line-height: 1.8;
            border: 2px dashed #ff3355; padding: 15px; background: #110005;
        }

        @keyframes zoomPulse {
            0% { transform: scale(1); }
            100% { transform: scale(1.06); }
        }

        /* In-Game HUD */
        #hud {
            position: absolute; bottom: 20px; width: 100%; display: flex; 
            justify-content: space-around; pointer-events: none; z-index: 10;
            font-size: 1.1rem; text-shadow: 0 0 10px #000;
        }
        .p1-hud { color: #00ff41; }
        .p2-hud { color: #ffcc00; }
        
        #match-info {
            position: absolute; top: 15px; width: 100%; display: flex; justify-content: center; gap: 50px;
            pointer-events: none; z-index: 10; color: #fff; font-size: 1.1rem;
            text-shadow: 0 0 8px #ff0033;
        }

        .dmg-popup {
            position: absolute; color: #ffcc00; font-weight: bold; font-size: 1.2rem;
            pointer-events: none; z-index: 20; text-shadow: 0 0 6px #ff0000;
            animation: floatUp 0.6s ease-out forwards;
        }

        .score-popup {
            position: absolute; color: #00ff41; font-weight: bold; font-size: 1.8rem;
            pointer-events: none; z-index: 25; text-shadow: 0 0 10px #00ff41;
            animation: scorePopupAnim 0.8s ease-out forwards;
        }

        @keyframes floatUp {
            0% { transform: translateY(0) scale(1.2); opacity: 1; }
            100% { transform: translateY(-40px) scale(0.8); opacity: 0; }
        }

        @keyframes scorePopupAnim {
            0% { transform: translateY(0) scale(1.4); opacity: 1; }
            70% { transform: translateY(-45px) scale(1.0); opacity: 1; }
            100% { transform: translateY(-70px) scale(0.8); opacity: 0; }
        }

        #bg-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 0; }
        
        /* Pixel-crunch style rendering for Three.js canvas */
        canvas:not(#bg-canvas) { 
            position: relative; 
            z-index: 2; 
            image-rendering: pixelated; 
        }


        /* Stage Select / Five Code-Themed Stages */
        #stage-select-screen { background: #05070a; }
        .stage-grid {
            display: grid;
            grid-template-columns: repeat(5, minmax(135px, 1fr));
            gap: 12px;
            width: min(920px, 94vw);
            margin-top: 18px;
        }
        .stage-card {
            min-height: 185px;
            background: #090d12;
            border: 3px solid #333;
            padding: 10px;
            box-sizing: border-box;
            cursor: pointer;
            transition: transform .1s, box-shadow .1s, border-color .1s;
            text-align: center;
        }
        .stage-card:hover, .stage-card.selected {
            transform: translateY(-4px) scale(1.03);
            border-color: #00ff41;
            box-shadow: 0 0 18px rgba(0,255,65,.45);
        }
        .stage-preview {
            height: 105px;
            border: 2px solid #222;
            margin-bottom: 10px;
            position: relative;
            overflow: hidden;
            background: #020404;
        }
        .stage-preview canvas {
            width: 100%;
            height: 100%;
            display: block;
            image-rendering: pixelated;
        }
        .stage-preview-label {
            position: absolute;
            left: 5px;
            bottom: 4px;
            padding: 2px 4px;
            background: rgba(0,0,0,.72);
            color: #fff;
            font: 7px monospace;
            pointer-events: none;
        }
        .stage-card h3 { margin: 4px 0; font-size: .62rem; color: #00ff41; }
        .stage-card p { margin: 5px 0 0; color: #777; font-size: .42rem; line-height: 1.45; }
        .stage-status { color: #ffcc00; font-size: .55rem; margin-top: 12px; }

        @keyframes glitchText {
            0% { transform: translate(0, 0); }
            20% { transform: translate(-2px, 2px); }
            40% { transform: translate(2px, -2px); }
            60% { transform: translate(-1px, -1px); }
            80% { transform: translate(1px, 1px); }
            100% { transform: translate(0, 0); }
        }
    </style>
</head>
<body>

    <!-- Retro Filter Overlay -->
    <div id="gamecube-filter"></div>

    <!-- Finisher Cinematic Flash Overlay -->
    <div id="finisher-flash"></div>

    <!-- Countdown 3-2-1-GO Overlay -->
    <div id="countdown-overlay" class="hidden">
        <div id="countdown-display" class="countdown-text">3</div>
    </div>

    <!-- 1. MAIN TITLE SCREEN -->
    <div id="title-screen" class="screen">
        <h1>NEGA 64</h1>
        <h2>ABSTRACT DYNAMIC TILT UPDATE</h2>
        <div class="menu-options">
            <button class="menu-btn selected" onclick="goToRules()">1. VS BATTLE</button>
            <button class="menu-btn" onclick="openStats()">2. SYSTEM STATS</button>
        </div>
        <p style="color: #666; font-size: 0.6rem; margin-top: 50px;">PRESS [ENTER] OR CLICK TO START</p>
    </div>

    <!-- 2. RULES SCREEN -->
    <div id="rules-screen" class="screen hidden">
        <h1 style="color: #ff3355; font-size: 1.8rem; text-shadow: 0 0 10px #ff0033;">MATCH RULES</h1>
        <div class="rules-container">
            <div class="rule-box">
                <h3>GAME MODE</h3>
                <div class="rule-val" id="rule-mode-display" style="color: #ffcc00;">STOCK</div>
                <p style="font-size: 0.5rem; color: #aaa;">[W/S] Toggle Mode</p>
            </div>
            <div class="rule-box" id="stock-rule-container">
                <h3>STOCKS</h3>
                <div class="rule-val" id="rule-stock-display">3</div>
                <p style="font-size: 0.5rem; color: #aaa;">[Up/Down] Change</p>
            </div>
            <div class="rule-box">
                <h3>TIME LIMIT</h3>
                <div class="rule-val" id="rule-time-display">INF</div>
                <p style="font-size: 0.5rem; color: #aaa;">[A/D] Change</p>
            </div>
        </div>
        <button class="menu-btn selected" onclick="goToCharSelect()" style="margin-top: 10px;">CONTINUE TO SELECT ></button>
    </div>

    <!-- 3. SYSTEM STATS SCREEN -->
    <div id="stats-screen" class="screen hidden">
        <h1 style="color: #00ff41; font-size: 1.5rem; text-shadow: 0 0 10px #00ff41;">SYSTEM METRICS</h1>
        <div class="stats-box">
            <p>TOTAL MATCHES: <span id="stat-matches" style="color:#ffcc00">0</span></p>
            <p>P1 K.O.s: <span id="stat-p1k0" style="color:#00ff41">0</span></p>
            <p>P2/CPU K.O.s: <span id="stat-p2k0" style="color:#ffcc00">0</span></p>
            <p>ENGINE STATUS: <span style="color:#00ff41">STABLE (60 FPS)</span></p>
        </div>
        <button class="menu-btn" onclick="closeStats()" style="margin-top: 30px;">< BACK TO MENU</button>
    </div>

    <!-- 4. CHARACTER SELECT SCREEN -->
    <div id="char-select-screen" class="screen hidden">
        <h1 style="text-shadow: 0 0 15px #ff0033; color: #ff3355; font-size: 1.6rem; margin-bottom: 5px;">CHARACTER SELECT</h1>
        <p style="color: #888; font-size: 0.6rem;">CHOOSE YOUR FIGHTER & P2 CONTROLLER</p>
        
        <div class="select-grid">
            <div class="char-card">
                <div style="color: #00ff41; font-weight: bold; font-size: 0.7rem;">0</div>
                <div class="char-preview" style="color: #00ff41;">0</div>
                <span style="font-size: 0.55rem; color: #aaa;">ZERO</span>
            </div>
            <div class="char-card">
                <div style="color: #00ff41; font-weight: bold; font-size: 0.7rem;">1</div>
                <div class="char-preview" style="color: #00ff41;">1</div>
                <span style="font-size: 0.55rem; color: #aaa;">ONE</span>
            </div>
            <div class="char-card">
                <div style="color: #00ff41; font-weight: bold; font-size: 0.7rem;">2</div>
                <div class="char-preview" style="font-size: 1.1rem;">{ }</div>
                <span style="font-size: 0.55rem; color: #aaa;">SYNTAX</span>
            </div>
            <div class="char-card">
                <div style="color: #00ff41; font-weight: bold; font-size: 0.7rem;">3</div>
                <div class="char-preview" style="color: #ff3355; font-size: 1.1rem;">#BUG</div>
                <span style="font-size: 0.55rem; color: #aaa;">BUG</span>
            </div>
            <div class="char-card">
                <div style="color: #00ff41; font-weight: bold; font-size: 0.7rem;">4</div>
                <div class="char-preview" style="color: #00ffcc; font-size: 1.1rem;">[::]</div>
                <span style="font-size: 0.55rem; color: #aaa;">STACK</span>
            </div>
        </div>

        <div class="selectors-status">
            <div class="player-select-box">
                <h3 style="margin: 0 0 5px 0;">PLAYER 1</h3>
                <p style="margin: 4px 0;">Choice: <span id="p1-choice-text" style="font-size: 0.9rem;">0</span></p>
                <p id="p1-status" style="margin: 4px 0; color: #ff3355;">[UNLOCKED]</p>
                <p style="font-size: 0.45rem; color: #888; margin-top: 3px;">[1-5] Switch | [Space] Lock</p>
            </div>
            <div class="player-select-box p2-box">
                <h3 style="margin: 0 0 5px 0;">PLAYER 2 / CPU</h3>
                <p style="margin: 4px 0;">Choice: <span id="p2-choice-text" style="font-size: 0.9rem;">1</span></p>
                <p style="margin: 4px 0;">Mode: <span id="p2-mode-text" style="font-size: 0.8rem; color:#00ff41;">CPU (LV 5)</span></p>
                <p id="p2-status" style="margin: 4px 0; color: #ff3355;">[UNLOCKED]</p>
                <p style="font-size: 0.45rem; color: #ccaa00; margin-top: 3px;">[Arrows] Switch | [C] Toggle CPU/Human | [+/-] CPU Lv | [Enter] Lock</p>
            </div>
        </div>
    </div>


    <!-- 5. STAGE SELECT SCREEN -->
    <div id="stage-select-screen" class="screen hidden">
        <h1 style="color:#00ff41; font-size:1.7rem; text-shadow:0 0 15px #00ff41;">STAGE SELECT</h1>
        <p style="color:#888; font-size:.58rem;">CHOOSE A CODE-THEMED BATTLEFIELD • SMASH-STYLE STAGE SELECT</p>
        <div class="stage-grid">
            <div class="stage-card selected" data-stage="0" onclick="selectStage(0)">
                <div class="stage-preview"><canvas id="stage-preview-0"></canvas><div class="stage-preview-label">LIVE PREVIEW</div></div>
                <h3>01 // TERMINAL</h3><p>Balanced compiler deck with two command platforms.</p>
            </div>
            <div class="stage-card" data-stage="1" onclick="selectStage(1)">
                <div class="stage-preview"><canvas id="stage-preview-1"></canvas><div class="stage-preview-label">LIVE PREVIEW</div></div>
                <h3>02 // ERROR ROOM</h3><p>Broken-code layout with offset platforms and a center gap.</p>
            </div>
            <div class="stage-card" data-stage="2" onclick="selectStage(2)">
                <div class="stage-preview"><canvas id="stage-preview-2"></canvas><div class="stage-preview-label">LIVE PREVIEW</div></div>
                <h3>03 // STACK OVERFLOW</h3><p>Vertical stack layout built around staggered ledges.</p>
            </div>
            <div class="stage-card" data-stage="3" onclick="selectStage(3)">
                <div class="stage-preview"><canvas id="stage-preview-3"></canvas><div class="stage-preview-label">LIVE PREVIEW</div></div>
                <h3>04 // INFINITE LOOP</h3><p>Long central route with looping upper platforms.</p>
            </div>
            <div class="stage-card" data-stage="4" onclick="selectStage(4)">
                <div class="stage-preview"><canvas id="stage-preview-4"></canvas><div class="stage-preview-label">LIVE PREVIEW</div></div>
                <h3>05 // SOURCE VAULT</h3><p>Wide vault arena with a high split-platform layer.</p>
            </div>
        </div>
        <div class="stage-status">SELECTED: <span id="stage-choice-text">01 // TERMINAL</span> | [1-5] SWITCH | [ENTER] START</div>
    </div>

    <!-- Game Over / Victory Overlay Sequence -->
    <div id="game-overlay">
        <div id="game-callout-text" class="game-callout">GAME!</div>
        <div id="victory-container">
            <div class="winner-callout" id="winner-text">PLAYER 1 WINS</div>
            <div class="final-stats-summary" id="final-stats-text">P1 SCORE: 3 | P2 SCORE: 1</div>
            <button class="menu-btn selected" onclick="location.reload()" style="font-size: 0.8rem;">MAIN MENU</button>
        </div>
    </div>

    <!-- Match Overlay HUD -->
    <div id="match-info" class="hidden">
        <div>STAGE: <span id="hud-stage-name">01 // TERMINAL</span> | TIME: <span id="hud-time">INF</span></div>
    </div>

    <div id="hud" class="hidden">
        <div class="p1-hud">P1 (<span id="p1-char-hud">0</span>) | <span id="p1-hud-label">STOCKS: <span id="p1-stocks">3</span></span> | <span id="p1-pct">0</span>%</div>
        <div class="p2-hud"><span id="p2-label-name">P2</span> (<span id="p2-char-hud">1</span>) | <span id="p2-hud-label">STOCKS: <span id="p2-stocks">3</span></span> | <span id="p2-pct">0</span>%</div>
    </div>

    <canvas id="bg-canvas"></canvas>

    <!-- Three.js Library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

    <script>
        // --- Audio & BGM Setup using Raw GitHub Links ---
        const audioBaseUrl = "https://raw.githubusercontent.com/noxxerw2007-crypto/quick-minecraft-hit-audio/main/";
        
        const sfx = {
            swing: new Audio(audioBaseUrl + "Punch%20swing.mp3"),
            hit: new Audio(audioBaseUrl + "Hitsound%20from%20Super%20Smash%20Bros%20Melee.mp3"),
            crit: new Audio(audioBaseUrl + "Smash%20Bros%20Death%20Sound%20Effect.mp3"),
            death: new Audio(audioBaseUrl + "Super%20Smash%20Bros%20(N64)%20-%20Blast%20KO%20Sound.mp3"),
            game: new Audio(audioBaseUrl + "Super%20Smash%20Bros.%20Melee%20GAME!%20sound%20effect.mp3"),
            select: new Audio(audioBaseUrl + "Melee%20Menu%20Select%20Sound.mp3"),
            announcer321: new Audio(audioBaseUrl + "Making%20The%20(SSBM)%20Announcer%20Saying_%203...%202...%201...%20GO!%20Instead%20of_%20Ready...%20GO!.mp3")
        };

        const bgm = {
            menu: new Audio(audioBaseUrl + "Super%20Smash%20Bros.%20Melee%20-%20Battlefield%20-%20%5BAwedecai%20Remix%5D.mp3"),
            ingame: new Audio(audioBaseUrl + "Flat%20Zone%20-%20Super%20Smash%20Bros.%20Melee%20Remix.mp3"),
            victory: new Audio(audioBaseUrl + "Character%20Select%20-%20Super%20Smash%20Bros..mp3")
        };

        bgm.menu.loop = true;
        bgm.ingame.loop = true;
        bgm.victory.loop = false;
        
        bgm.menu.volume = 0.4;
        bgm.ingame.volume = 0.4;
        bgm.victory.volume = 0.5;

        let musicStarted = false;
        function tryStartMenuMusic() {
            if (!musicStarted) {
                musicStarted = true;
                bgm.menu.play().catch(e => {});
            }
        }
        window.addEventListener('click', tryStartMenuMusic);
        window.addEventListener('keydown', tryStartMenuMusic);

        function playSfx(soundObj) {
            try {
                const clone = soundObj.cloneNode();
                clone.volume = 0.6;
                clone.play().catch(e => {});
            } catch (err) {
                soundObj.currentTime = 0;
                soundObj.play().catch(e => {});
            }
        }

        let currentGameState = 'TITLE';
        let matchRules = { mode: 'STOCK', stocks: 3, time: 'INF' };
        let statsData = { matches: 0, p1KOs: 0, p2KOs: 0 };
        let matchEnded = false;
        let matchTimerSeconds = 0;
        let timerInterval = null;
        let matchIsLocked = true;
        
        const choices = { p1: 0, p2: 1 };
        const lockedIn = { p1: false, p2: false };
        let p2IsCPU = true;
        let cpuLevel = 5;

        let finisherTimer = 0;

        // Five selectable code-themed stages.
        let selectedStage = 0;
        let activeStageName = '01 // TERMINAL';
        let stageDecorGroup = null;
        const stageDefinitions = [
            {
                name: '01 // TERMINAL',
                bg: { base: '#050b07', accent: '#00ff41', trail: 'rgba(0,18,8,0.28)', glyphs: ['0','1','{','}',';','>'] },
                platforms: [
                    [24, -2, 4, 0, -2], [6, .6, 3, -6, 1.2], [6, .6, 3, 6, 1.2]
                ]
            },
            {
                name: '02 // ERROR ROOM',
                bg: { base: '#100407', accent: '#ff3355', trail: 'rgba(24,0,4,0.28)', glyphs: ['#','!','404','ERR','?','/'] },
                platforms: [
                    [18, 1, 4, 0, -2], [6, .6, 3, -8, 2.4], [6, .6, 3, 8, 2.4], [5, .55, 3, -2.5, 4.5], [5, .55, 3, 4.5, 5.7]
                ]
            },
            {
                name: '03 // STACK OVERFLOW',
                bg: { base: '#031017', accent: '#00ffcc', trail: 'rgba(0,15,24,0.28)', glyphs: ['[ ]','::','PUSH','POP','{ }','=>'] },
                platforms: [
                    [16, 1, 4, 0, -2], [5, .55, 3, -6.5, 1], [5, .55, 3, 6.5, 1],
                    [4.5, .55, 3, -3.5, 4], [4.5, .55, 3, 3.5, 6.5], [3.8, .5, 3, -1, 9]
                ]
            },
            {
                name: '04 // INFINITE LOOP',
                bg: { base: '#100d03', accent: '#ffcc00', trail: 'rgba(20,12,0,0.28)', glyphs: ['while','1','for','++','--','=>'] },
                platforms: [
                    [26, 1, 4, 0, -2], [5.5, .55, 3, -7, 2], [5.5, .55, 3, 7, 2],
                    [4.5, .5, 3, -9, 5.5], [4.5, .5, 3, 9, 5.5], [7, .55, 3, 0, 6.8]
                ]
            },
            {
                name: '05 // SOURCE VAULT',
                bg: { base: '#080416', accent: '#b58cff', trail: 'rgba(10,2,24,0.28)', glyphs: ['<div>','</>','CSS','JS','HTML','{}'] },
                platforms: [
                    [28, 1, 4, 0, -2], [7, .6, 3, -7, 2.2], [7, .6, 3, 7, 2.2],
                    [5, .55, 3, -3.5, 6], [5, .55, 3, 3.5, 6], [3.5, .5, 3, 0, 9]
                ]
            }
        ];



        // --- Smash-style live stage thumbnails ---
        function renderStageThumbnail(index) {
            const canvas = document.getElementById('stage-preview-' + index);
            if (!canvas) return;
            const def = stageDefinitions[index];
            const rect = canvas.getBoundingClientRect();
            const w = Math.max(240, Math.floor(rect.width * 2));
            const h = Math.max(160, Math.floor(rect.height * 2));
            canvas.width = w; canvas.height = h;

            const ctx = canvas.getContext('2d');
            ctx.fillStyle = def.bg.base;
            ctx.fillRect(0, 0, w, h);

            // Code-grid backdrop.
            ctx.globalAlpha = 0.22;
            ctx.fillStyle = def.bg.accent;
            ctx.font = '12px monospace';
            for (let x = 0; x < w; x += 42) {
                for (let y = 14; y < h; y += 22) {
                    ctx.fillText(def.bg.glyphs[(x / 42 + y / 22 + index) % def.bg.glyphs.length], x, y);
                }
            }
            ctx.globalAlpha = 1;

            // Convert the actual world layout into a miniature Smash-like view.
            const worldLeft = -16, worldRight = 16, worldBottom = -4, worldTop = 11;
            const sx = w / (worldRight - worldLeft);
            const sy = h / (worldTop - worldBottom);

            function px(x) { return (x - worldLeft) * sx; }
            function py(y) { return h - (y - worldBottom) * sy; }

            def.platforms.forEach((spec, i) => {
                const [pw, ph, pd, x, y] = spec;
                const left = px(x - pw / 2);
                const top = py(y + ph / 2);
                const width = pw * sx;
                const height = Math.max(4, ph * sy);

                ctx.fillStyle = 'rgba(0,0,0,.65)';
                ctx.fillRect(left + 2, top + 3, width, height);

                ctx.fillStyle = def.bg.accent;
                ctx.fillRect(left, top, width, height);

                ctx.fillStyle = 'rgba(255,255,255,.28)';
                ctx.fillRect(left, top, width, Math.max(2, height * .18));

                ctx.strokeStyle = '#fff';
                ctx.globalAlpha = .35;
                ctx.strokeRect(left, top, width, height);
                ctx.globalAlpha = 1;
            });

            // Tiny fighter silhouettes showing the scale of the arena.
            const ground = def.platforms[0];
            const fighterY = ground[4] + ground[1] / 2 + 0.9;
            [ground[3] - 3, ground[3] + 3].forEach((fx, i) => {
                ctx.fillStyle = i === 0 ? '#00ff41' : '#ffcc00';
                ctx.fillRect(px(fx) - 3, py(fighterY) - 8, 6, 8);
                ctx.fillRect(px(fx) - 5, py(fighterY) - 3, 10, 3);
            });
        }

        function renderAllStageThumbnails() {
            for (let i = 0; i < stageDefinitions.length; i++) renderStageThumbnail(i);
        }

        window.addEventListener('resize', () => {
            if (currentGameState === 'STAGE_SELECT') renderAllStageThumbnails();
        });

        function selectStage(index) {
            selectedStage = ((index % stageDefinitions.length) + stageDefinitions.length) % stageDefinitions.length;
            activeStageName = stageDefinitions[selectedStage].name;
            document.querySelectorAll('.stage-card').forEach((card, i) => card.classList.toggle('selected', i === selectedStage));
            document.getElementById('stage-choice-text').innerText = activeStageName;
            playSfx(sfx.select);
            applyStage(selectedStage);
            requestAnimationFrame(renderAllStageThumbnails);
        }

        function goToStageSelect() {
            playSfx(sfx.select);
            document.getElementById('char-select-screen').classList.add('hidden');
            document.getElementById('stage-select-screen').classList.remove('hidden');
            currentGameState = 'STAGE_SELECT';
            selectStage(selectedStage);
            requestAnimationFrame(renderAllStageThumbnails);
        }

        function startSelectedStage() {
            currentGameState = 'PLAYING';
            statsData.matches++;
            bgm.menu.pause();
            bgm.menu.currentTime = 0;
            bgm.ingame.play().catch(e => {});
            document.getElementById('stage-select-screen').classList.add('hidden');
            document.getElementById('stage-select-screen').style.display = '';
            document.getElementById('match-info').classList.remove('hidden');
            document.getElementById('hud').classList.remove('hidden');
            runCountdownSequence();
        }

        function goToRules() {
            playSfx(sfx.select);
            document.getElementById('title-screen').classList.add('hidden');
            document.getElementById('rules-screen').classList.remove('hidden');
            currentGameState = 'RULES';
        }

        function openStats() {
            playSfx(sfx.select);
            document.getElementById('title-screen').classList.add('hidden');
            document.getElementById('stats-screen').classList.remove('hidden');
            currentGameState = 'STATS';
            document.getElementById('stat-matches').innerText = statsData.matches;
            document.getElementById('stat-p1k0').innerText = statsData.p1KOs;
            document.getElementById('stat-p2k0').innerText = statsData.p2KOs;
        }

        function closeStats() {
            playSfx(sfx.select);
            document.getElementById('stats-screen').classList.add('hidden');
            document.getElementById('title-screen').classList.remove('hidden');
            currentGameState = 'TITLE';
        }

        function goToCharSelect() {
            playSfx(sfx.select);
            document.getElementById('rules-screen').classList.add('hidden');
            document.getElementById('char-select-screen').classList.remove('hidden');
            currentGameState = 'CHAR_SELECT';
        }

        window.addEventListener('keydown', (e) => {
            if (currentGameState === 'RULES') {
                if (e.code === 'KeyW' || e.code === 'KeyS' || e.code === 'ArrowUp' || e.code === 'ArrowDown') {
                    playSfx(sfx.select);
                    if (matchRules.mode === 'STOCK') {
                        matchRules.mode = 'STATS';
                        matchRules.time = '3:00';
                        document.getElementById('rule-mode-display').innerText = 'STATS';
                        document.getElementById('rule-mode-display').style.color = '#00ff41';
                        document.getElementById('stock-rule-container').style.opacity = '0.3';
                        document.getElementById('rule-time-display').innerText = matchRules.time;
                    } else {
                        matchRules.mode = 'STOCK';
                        matchRules.time = 'INF';
                        document.getElementById('rule-mode-display').innerText = 'STOCK';
                        document.getElementById('rule-mode-display').style.color = '#ffcc00';
                        document.getElementById('stock-rule-container').style.opacity = '1';
                        document.getElementById('rule-time-display').innerText = matchRules.time;
                    }
                }
                if (matchRules.mode === 'STOCK') {
                    if (e.code === 'ArrowUp') {
                        playSfx(sfx.select);
                        matchRules.stocks = matchRules.stocks >= 9 ? 1 : matchRules.stocks + 1;
                        document.getElementById('rule-stock-display').innerText = matchRules.stocks;
                    }
                    if (e.code === 'ArrowDown') {
                        playSfx(sfx.select);
                        matchRules.stocks = matchRules.stocks <= 1 ? 9 : matchRules.stocks - 1;
                        document.getElementById('rule-stock-display').innerText = matchRules.stocks;
                    }
                }
                if (e.code === 'KeyD' || e.code === 'ArrowRight' || e.code === 'KeyA' || e.code === 'ArrowLeft') {
                    playSfx(sfx.select);
                    if (matchRules.mode === 'STATS') {
                        matchRules.time = matchRules.time === '3:00' ? '2:00' : '3:00';
                    } else {
                        matchRules.time = matchRules.time === 'INF' ? '3:00' : (matchRules.time === '3:00' ? '2:00' : 'INF');
                    }
                    document.getElementById('rule-time-display').innerText = matchRules.time;
                }
                if (e.code === 'Enter') goToCharSelect();
            } else if (currentGameState === 'STAGE_SELECT') {
                if (e.code === 'Digit1') selectStage(0);
                if (e.code === 'Digit2') selectStage(1);
                if (e.code === 'Digit3') selectStage(2);
                if (e.code === 'Digit4') selectStage(3);
                if (e.code === 'Digit5') selectStage(4);
                if (e.code === 'ArrowLeft') selectStage(selectedStage - 1);
                if (e.code === 'ArrowRight') selectStage(selectedStage + 1);
                if (e.code === 'Enter') startSelectedStage();
            } else if (currentGameState === 'CHAR_SELECT') {
                if (['Digit1', 'Digit2', 'Digit3', 'Digit4', 'Digit5'].includes(e.code)) {
                    playSfx(sfx.select);
                    choices.p1 = parseInt(e.code.replace('Digit', '')) - 1;
                    document.getElementById('p1-choice-text').innerText = choices.p1;
                }
                if (e.code === 'Space') {
                    playSfx(sfx.select);
                    lockedIn.p1 = true;
                    p1.charType = choices.p1;
                    document.getElementById('p1-char-hud').innerText = p1.charType;
                    setupHudForMode();
                    rebuildPlayerMesh(p1);
                    document.getElementById('p1-status').innerText = '[LOCKED]';
                    document.getElementById('p1-status').style.color = '#00ff41';
                }

                if (e.code === 'KeyC') {
                    playSfx(sfx.select);
                    p2IsCPU = !p2IsCPU;
                    document.getElementById('p2-mode-text').innerText = p2IsCPU ? `CPU (LV ${cpuLevel})` : 'HUMAN';
                    document.getElementById('p2-label-name').innerText = p2IsCPU ? `CPU` : `P2`;
                }
                if (p2IsCPU && (e.code === 'Equal' || e.code === 'NumpadAdd')) {
                    playSfx(sfx.select);
                    cpuLevel = Math.min(10, cpuLevel + 1);
                    document.getElementById('p2-mode-text').innerText = `CPU (LV ${cpuLevel})`;
                }
                if (p2IsCPU && (e.code === 'Minus' || e.code === 'NumpadSubtract')) {
                    playSfx(sfx.select);
                    cpuLevel = Math.max(1, cpuLevel - 1);
                    document.getElementById('p2-mode-text').innerText = `CPU (LV ${cpuLevel})`;
                }

                if (e.code === 'ArrowUp') { playSfx(sfx.select); choices.p2 = (choices.p2 + 1) % 5; document.getElementById('p2-choice-text').innerText = choices.p2; }
                if (e.code === 'ArrowDown') { playSfx(sfx.select); choices.p2 = (choices.p2 - 1 + 5) % 5; document.getElementById('p2-choice-text').innerText = choices.p2; }
                if (e.code === 'Enter') {
                    playSfx(sfx.select);
                    lockedIn.p2 = true;
                    p2.charType = choices.p2;
                    document.getElementById('p2-char-hud').innerText = p2.charType;
                    setupHudForMode();
                    rebuildPlayerMesh(p2);
                    document.getElementById('p2-status').innerText = '[LOCKED]';
                    document.getElementById('p2-status').style.color = '#ffcc00';
                }

                if (lockedIn.p1 && lockedIn.p2) {
                    currentGameState = 'PLAYING';
                    statsData.matches++;
                    
                    bgm.menu.pause();
                    bgm.menu.currentTime = 0;
                    bgm.ingame.play().catch(e => {});

                    document.getElementById('char-select-screen').style.display = 'none';
                    document.getElementById('match-info').classList.remove('hidden');
                    document.getElementById('hud').classList.remove('hidden');
                    
                    runCountdownSequence();
                }
            }
        });

        function runCountdownSequence() {
            matchIsLocked = true;
            const overlay = document.getElementById('countdown-overlay');
            const display = document.getElementById('countdown-display');
            overlay.classList.remove('hidden');

            playSfx(sfx.announcer321);

            let steps = ['3', '2', '1', 'GO!'];
            let index = 0;

            function nextStep() {
                if (index < steps.length) {
                    display.innerText = steps[index];
                    index++;
                    let delay = (index === steps.length) ? 800 : 1000;
                    setTimeout(nextStep, delay);
                } else {
                    overlay.classList.add('hidden');
                    matchIsLocked = false;
                    startMatchTimer();
                }
            }
            setTimeout(nextStep, 1000);
        }

        function setupHudForMode() {
            if (matchRules.mode === 'STOCK') {
                p1.stocks = matchRules.stocks;
                p2.stocks = matchRules.stocks;
                document.getElementById('p1-hud-label').innerHTML = 'STOCKS: <span id="p1-stocks">' + p1.stocks + '</span>';
                document.getElementById('p2-hud-label').innerHTML = 'STOCKS: <span id="p2-stocks">' + p2.stocks + '</span>';
            } else {
                p1.score = 0; p2.score = 0;
                document.getElementById('p1-hud-label').innerHTML = 'PTS: <span id="p1-pts">0</span>';
                document.getElementById('p2-hud-label').innerHTML = 'PTS: <span id="p2-pts">0</span>';
            }
            document.getElementById('hud-time').innerText = matchRules.time;
            const stageHud = document.getElementById('hud-stage-name');
            if (stageHud) stageHud.innerText = activeStageName;
        }

        function startMatchTimer() {
            if (matchRules.time === 'INF') return;
            const parts = matchRules.time.split(':');
            matchTimerSeconds = parseInt(parts[0]) * 60 + parseInt(parts[1]);
            
            timerInterval = setInterval(() => {
                if (matchEnded) { clearInterval(timerInterval); return; }
                matchTimerSeconds--;
                let m = Math.floor(matchTimerSeconds / 60);
                let s = matchTimerSeconds % 60;
                document.getElementById('hud-time').innerText = m + ':' + (s < 10 ? '0' : '') + s;
                
                if (matchTimerSeconds <= 0) {
                    clearInterval(timerInterval);
                    triggerGameOver();
                }
            }, 1000);
        }

        // --- Background Setup ---
        const bgCanvas = document.getElementById('bg-canvas');
        const bgCtx = bgCanvas.getContext('2d');
        function resizeBg() { bgCanvas.width = window.innerWidth; bgCanvas.height = window.innerHeight; }
        window.addEventListener('resize', resizeBg);
        resizeBg();

        const fontSize = 18;
        let columns = Math.floor(bgCanvas.width / fontSize);
        let drops = Array(columns).fill(1);

        function drawAnimatedBg() {
            if (currentGameState === 'PLAYING' || currentGameState === 'STAGE_SELECT') {
                const def = stageDefinitions[selectedStage];
                bgCtx.fillStyle = def.bg.base;
                bgCtx.fillRect(0, 0, bgCanvas.width, bgCanvas.height);
                drawStageBackdrop();

                // Preserve the original binary rain as an additional code layer.
                bgCtx.fillStyle = def.bg.accent;
                bgCtx.globalAlpha = .35;
                bgCtx.font = 'bold ' + fontSize + 'px monospace';
                for (let i = 0; i < drops.length; i++) {
                    const text = Math.random() < 0.5 ? '0' : '1';
                    bgCtx.fillText(text, i * fontSize, drops[i] * fontSize);
                    if (drops[i] * fontSize > bgCanvas.height && Math.random() > 0.975) drops[i] = 0;
                    drops[i]++;
                }
                bgCtx.globalAlpha = 1;
            } else {
                bgCtx.fillStyle = 'rgba(10, 0, 2, 0.25)';
                bgCtx.fillRect(0, 0, bgCanvas.width, bgCanvas.height);
                bgCtx.fillStyle = '#ff0033';
                bgCtx.font = 'bold ' + fontSize + 'px monospace';
                for (let i = 0; i < drops.length; i++) {
                    const text = Math.random() < 0.5 ? '0' : '1';
                    bgCtx.fillText(text, i * fontSize, drops[i] * fontSize);
                    if (drops[i] * fontSize > bgCanvas.height && Math.random() > 0.975) drops[i] = 0;
                    drops[i]++;
                }
            }
        }

        // --- Three.js Setup with Pixel Scale Factor matching prototype ---
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        
        // Tuned sweet-spot pixel scale factor integrated from prototype
        const pixelScaleFactor = 2.2; 
        renderer.setPixelRatio(Math.max(window.devicePixelRatio / pixelScaleFactor, 0.5));
        
        document.body.appendChild(renderer.domElement);

        scene.add(new THREE.AmbientLight(0x002205, 1.0));
        const dirLight = new THREE.DirectionalLight(0x00ff41, 1.5);
        dirLight.position.set(5, 10, 10);
        scene.add(dirLight);

        // Stage & Platforms
        const platformWidth = 24;
        const platformTextureCanvas = document.createElement('canvas');
        platformTextureCanvas.width = 512; platformTextureCanvas.height = 128;
        const pCtx = platformTextureCanvas.getContext('2d');
        pCtx.fillStyle = '#0a140a'; pCtx.fillRect(0, 0, 512, 128);
        pCtx.fillStyle = '#ffcc00'; pCtx.font = 'bold 16px monospace';
        for (let x = 0; x < 512; x += 24) {
            for (let y = 16; y < 128; y += 24) {
                pCtx.fillText(Math.random() < 0.5 ? '010' : '101', x, y);
            }
        }
        const platformTex = new THREE.CanvasTexture(platformTextureCanvas);
        platformTex.magFilter = THREE.NearestFilter; platformTex.minFilter = THREE.NearestFilter;

        const mainPlatformMat = new THREE.MeshStandardMaterial({ map: platformTex, roughness: 0.6, metalness: 0.4, flatShading: true });
        
        const platform = new THREE.Mesh(new THREE.BoxGeometry(platformWidth, 1, 4), mainPlatformMat);
        platform.position.set(0, -2, 0);
        scene.add(platform);

        const platLeft = new THREE.Mesh(new THREE.BoxGeometry(6, 0.6, 3), mainPlatformMat);
        platLeft.position.set(-6, 1.2, 0);
        scene.add(platLeft);

        const platRight = new THREE.Mesh(new THREE.BoxGeometry(6, 0.6, 3), mainPlatformMat);
        platRight.position.set(6, 1.2, 0);
        scene.add(platRight);

        const platforms = [platform, platLeft, platRight];


        // --- Added Five-Stage System ---
        // The original prototype platforms remain intact; they are hidden while a selected
        // stage is active, so no original gameplay systems are removed.
        const originalPlatforms = platforms.slice();
        originalPlatforms.forEach(p => p.visible = false);

        function makeStageMaterial(def) {
            const c = document.createElement('canvas');
            c.width = 512; c.height = 128;
            const ctx = c.getContext('2d');
            ctx.fillStyle = def.bg.base; ctx.fillRect(0, 0, c.width, c.height);
            ctx.fillStyle = def.bg.accent;
            ctx.font = 'bold 16px monospace';
            for (let x = 0; x < c.width; x += 64) {
                for (let y = 18; y < c.height; y += 30) {
                    ctx.fillText(def.bg.glyphs[(x / 64 + y / 30) % def.bg.glyphs.length], x, y);
                }
            }
            const tex = new THREE.CanvasTexture(c);
            tex.magFilter = THREE.NearestFilter;
            tex.minFilter = THREE.NearestFilter;
            return new THREE.MeshStandardMaterial({ map: tex, roughness: .7, metalness: .25, flatShading: true });
        }

        function buildStageGeometry(def) {
            if (stageDecorGroup) scene.remove(stageDecorGroup);
            stageDecorGroup = new THREE.Group();

            const mat = makeStageMaterial(def);
            def.platforms.forEach(spec => {
                const [w, h, d, x, y] = spec;
                const mesh = new THREE.Mesh(new THREE.BoxGeometry(w, h, d), mat.clone());
                mesh.position.set(x, y, 0);
                stageDecorGroup.add(mesh);
            });

            // Decorative floating code panels make every stage visually distinct.
            const panelMat = new THREE.MeshBasicMaterial({ color: def.bg.accent, transparent: true, opacity: .16 });
            const panelCount = selectedStage + 3;
            for (let i = 0; i < panelCount; i++) {
                const panel = new THREE.Mesh(new THREE.BoxGeometry(2.5 + (i % 2), .08, .25), panelMat);
                panel.position.set(-10 + i * (20 / Math.max(1, panelCount - 1)), 8 + (i % 3) * 2, -1);
                panel.rotation.z = (i % 2 ? -.08 : .08);
                stageDecorGroup.add(panel);
            }

            scene.add(stageDecorGroup);
            // Rebuild the collision list from the actual stage platform specs.
            // Decorative code panels are intentionally NOT collision platforms.
            platforms.length = 0;
            def.platforms.forEach(spec => {
                const [w, h, d, x, y] = spec;
                const collisionMesh = new THREE.Mesh(
                    new THREE.BoxGeometry(w, h, d),
                    new THREE.MeshBasicMaterial({ visible: false })
                );
                collisionMesh.position.set(x, y, 0);
                collisionMesh.updateMatrixWorld(true);
                scene.add(collisionMesh);
                platforms.push(collisionMesh);
            });

            // Spawn points adapt to each layout.
            p1.spawnX = -4; p2.spawnX = 4;
            p1.spawnY = def.platforms[0][4] + 1.05;
            p2.spawnY = def.platforms[0][4] + 1.05;
            p1.x = p1.spawnX; p2.x = p2.spawnX;
            p1.y = p1.spawnY; p2.y = p2.spawnY;
        }

        function applyStage(index) {
            const def = stageDefinitions[index];
            activeStageName = def.name;
            buildStageGeometry(def);
            blastzone.left = -30;
            blastzone.right = 30;
            blastzone.top = 18;
            blastzone.bottom = -17;
            document.getElementById('hud-time').innerText = matchRules.time;
        }

        function drawStageBackdrop() {
            const def = stageDefinitions[selectedStage];
            bgCtx.fillStyle = def.bg.trail;
            bgCtx.fillRect(0, 0, bgCanvas.width, bgCanvas.height);
            bgCtx.fillStyle = def.bg.accent;
            bgCtx.font = 'bold 16px monospace';

            const spacing = 70;
            const cols = Math.ceil(bgCanvas.width / spacing);
            const rows = Math.ceil(bgCanvas.height / 38);
            for (let i = 0; i < cols; i++) {
                for (let j = 0; j < rows; j++) {
                    if ((i * 7 + j * 3 + selectedStage) % 5 === 0) {
                        const glyph = def.bg.glyphs[(i + j) % def.bg.glyphs.length];
                        bgCtx.globalAlpha = .13 + ((i + j) % 4) * .03;
                        bgCtx.fillText(glyph, i * spacing, j * 38 + 25);
                    }
                }
            }
            bgCtx.globalAlpha = 1;
        }

        const BASE_GREEN = 0x005511;
        const DARK_GREEN = 0x001504;
        const WHITE_FLASH = 0xffffff;
        const blastzone = { left: -26, right: 26, top: 18, bottom: -16 };

        const gravity = -0.016;        
        const maxWalkSpeed = 0.11;
        const groundAccel = 0.015;     
        const friction = 0.85;           
        const airMobility = 0.013;     
        const jumpForce = 0.38;
        const shortHopMult = 0.55;     
        const charHalfHeight = 0.85;   

        let hitstopFrames = 0;

        const p1 = { x: -4, y: -0.65, vx: 0, vy: 0, mesh: null, spawnX: -4, spawnY: -0.65, isJumping: false, facing: 1, charType: 0, attackType: null, attackCooldown: 0, attackAnimTimer: 0, stunTimer: 0, hitRandomRotX: 0, hitRandomRotY: 0, hitRandomRotZ: 0, percent: 0, stocks: 3, score: 0, respawnTimer: 0, isDead: false };
        const p2 = { x: 4, y: -0.65, vx: 0, vy: 0, mesh: null, spawnX: 4, spawnY: -0.65, isJumping: false, facing: -1, charType: 1, attackType: null, attackCooldown: 0, attackAnimTimer: 0, stunTimer: 0, hitRandomRotX: 0, hitRandomRotY: 0, hitRandomRotZ: 0, percent: 0, stocks: 3, score: 0, respawnTimer: 0, isDead: false };

        const projectiles = [];
        const activeHitboxes = []; 
        const hitSparks = [];
        const sparkGeo = new THREE.BoxGeometry(0.12, 0.12, 0.12);

        function spawnHitSparks(x, y, isSmash = false) {
            for (let i = 0; i < (isSmash ? 16 : 8); i++) {
                const spark = new THREE.Mesh(sparkGeo, new THREE.MeshBasicMaterial({ color: isSmash ? 0xff0033 : 0xffcc00 }));
                spark.position.set(x, y + 0.2, 0.3);
                const angle = Math.random() * Math.PI * 2;
                const speed = (isSmash ? 0.09 : 0.05) + Math.random() * 0.08;
                scene.add(spark);
                hitSparks.push({ mesh: spark, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: 12 });
            }
        }

        function spawnDeathEffect(x, y) {
            playSfx(sfx.death);
            for (let i = 0; i < 35; i++) {
                const spark = new THREE.Mesh(sparkGeo, new THREE.MeshBasicMaterial({ color: Math.random() < 0.5 ? 0xff3355 : 0xffcc00 }));
                spark.position.set(x, y, 0);
                const angle = Math.random() * Math.PI * 2;
                const speed = 0.08 + Math.random() * 0.22;
                scene.add(spark);
                hitSparks.push({ mesh: spark, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: 25 });
            }
        }

        // Pure Abstract N64 Geometry Meshes (No Limbs/Hands)
        function createN64Mesh(type) {
            const group = new THREE.Group();
            const mat = new THREE.MeshStandardMaterial({ color: BASE_GREEN, emissive: 0x002205, roughness: 0.4, flatShading: true });

            if (type === 0) {
                const ring = new THREE.Mesh(new THREE.TorusGeometry(0.52, 0.14, 6, 8), mat);
                ring.scale.set(0.95, 1.6, 1.0); 
                group.add(ring);
            } else if (type === 1) {
                const body = new THREE.Mesh(new THREE.CylinderGeometry(0.25, 0.35, 1.7, 5, 1), mat);
                body.position.y = 0.15;
                const flag = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.25, 0.35), mat);
                flag.position.set(-0.25, 0.85, 0); flag.rotation.z = 0.4;
                const base = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.2, 0.45), mat);
                base.position.y = -0.7;
                group.add(body); group.add(flag); group.add(base);
            } else if (type === 2) {
                const shapeL = new THREE.Shape();
                shapeL.moveTo(0, 0.8); shapeL.lineTo(-0.25, 0.8); shapeL.lineTo(-0.25, 0.35); shapeL.lineTo(-0.45, 0.15);
                shapeL.lineTo(-0.25, -0.05); shapeL.lineTo(-0.25, -0.8); shapeL.lineTo(0, -0.8); shapeL.lineTo(0, -0.62);
                shapeL.lineTo(-0.15, -0.62); shapeL.lineTo(-0.15, -0.15); shapeL.lineTo(-0.32, 0.05); shapeL.lineTo(-0.15, 0.25);
                shapeL.lineTo(-0.15, 0.62); shapeL.lineTo(0, 0.62); shapeL.closePath();

                const extrudeSettings = { depth: 0.18, bevelEnabled: true, bevelSegments: 1, steps: 1, bevelSize: 0.03, bevelThickness: 0.03 };
                const braceGeomL = new THREE.ExtrudeGeometry(shapeL, extrudeSettings);
                braceGeomL.center();
                
                const leftBrace = new THREE.Mesh(braceGeomL, mat);
                leftBrace.position.set(-0.35, 0, 0);
                const rightBrace = leftBrace.clone();
                rightBrace.scale.set(-1, 1, 1);
                rightBrace.position.set(0.35, 0, 0);
                const centerNode = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.25, 0.25), mat);
                centerNode.position.set(0, 0, 0);

                group.add(leftBrace); group.add(rightBrace); group.add(centerNode);
            } else if (type === 3) {
                const body = new THREE.Mesh(new THREE.OctahedronGeometry(0.7, 0), mat);
                const spike1 = new THREE.Mesh(new THREE.ConeGeometry(0.2, 0.9, 4), mat);
                spike1.position.set(0.6, 0.5, 0.2); spike1.rotation.z = -0.9;
                const spike2 = spike1.clone();
                spike2.position.set(-0.6, -0.5, -0.2); spike2.rotation.z = 2.2;
                group.add(body); group.add(spike1); group.add(spike2);
            } else {
                const block1 = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.25, 0.6), mat);
                block1.position.y = -0.5;
                const block2 = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.25, 0.6), mat);
                block2.position.y = -0.15;
                const block3 = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.25, 0.6), mat);
                block3.position.y = 0.2;
                const block4 = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.25, 0.6), mat);
                block4.position.y = 0.55;
                group.add(block1); group.add(block2); group.add(block3); group.add(block4);
            }

            scene.add(group);
            return group;
        }

        function rebuildPlayerMesh(p) {
            if (p.mesh) scene.remove(p.mesh);
            p.mesh = createN64Mesh(p.charType);
        }

        p1.mesh = createN64Mesh(p1.charType);
        p2.mesh = createN64Mesh(p2.charType);
        applyStage(0);

        const keys = {};
        window.addEventListener('keydown', (e) => keys[e.code] = true);
        window.addEventListener('keyup', (e) => keys[e.code] = false);

        const camTargetPos = new THREE.Vector3(0, 0, 16);

        function showScorePopup(x, y, scoringPlayer) {
            const vector = new THREE.Vector3(x, y + 0.8, 0).project(camera);
            const popup = document.createElement('div');
            popup.className = 'score-popup';
            popup.innerText = '+1 PTS!';
            popup.style.color = scoringPlayer === p1 ? '#00ff41' : '#ffcc00';
            popup.style.left = ((vector.x * 0.5 + 0.5) * window.innerWidth) + 'px';
            popup.style.top = ((-(vector.y * 0.5 - 0.5) * window.innerHeight)) + 'px';
            document.body.appendChild(popup);
            setTimeout(() => popup.remove(), 800);
        }

        function triggerGameOver(forcedWinner = null) {
            if (matchEnded) return;
            matchEnded = true;
            playSfx(sfx.game);

            bgm.ingame.pause();
            bgm.ingame.currentTime = 0;
            bgm.victory.play().catch(e => {});

            let winner = forcedWinner;
            if (!winner) {
                if (matchRules.mode === 'STATS') {
                    if (p1.score > p2.score) winner = p1;
                    else if (p2.score > p1.score) winner = p2;
                    else winner = Math.random() < 0.5 ? p1 : p2;
                } else {
                    winner = p1.stocks > p2.stocks ? p1 : p2;
                }
            }

            const overlay = document.getElementById('game-overlay');
            const calloutText = document.getElementById('game-callout-text');
            const victoryContainer = document.getElementById('victory-container');
            const winnerText = document.getElementById('winner-text');
            const finalStatsText = document.getElementById('final-stats-text');

            overlay.classList.add('active');
            calloutText.style.display = 'block';
            victoryContainer.classList.remove('show');

            setTimeout(() => {
                calloutText.style.display = 'none';
                victoryContainer.classList.add('show');
                
                const winnerName = winner === p1 ? "PLAYER 1 WINS!" : (p2IsCPU ? "CPU WINS!" : "PLAYER 2 WINS!");
                winnerText.innerText = winnerName;
                
                if (matchRules.mode === 'STATS') {
                    finalStatsText.innerHTML = `FINAL STATS (POINTS MODE)<br>P1 SCORE: ${p1.score} | P2 SCORE: ${p2.score}`;
                } else {
                    finalStatsText.innerHTML = `FINAL STATS (STOCK MODE)<br>P1 K.O.s: ${statsData.p1KOs} | P2 K.O.s: ${statsData.p2KOs}`;
                }
            }, 3000);
        }

        function handleElimination(p, opponent) {
            if (p.isDead) return;
            p.isDead = true;
            
            spawnDeathEffect(p.x, p.y);
            if (p.mesh) p.mesh.visible = false;

            opponent.score++;
            if (matchRules.mode === 'STOCK') {
                p.stocks--;
                if (p === p1) {
                    document.getElementById('p1-stocks').innerText = Math.max(0, p.stocks);
                    statsData.p2KOs++;
                } else {
                    document.getElementById('p2-stocks').innerText = Math.max(0, p.stocks);
                    statsData.p1KOs++;
                }
            } else {
                if (p === p1) {
                    document.getElementById('p1-pts').innerText = p1.score;
                    statsData.p2KOs++;
                } else {
                    document.getElementById('p2-pts').innerText = p2.score;
                    statsData.p1KOs++;
                }
            }

            showScorePopup(p.x, p.y, opponent);

            if (matchRules.mode === 'STOCK' && p.stocks <= 0) {
                triggerGameOver(opponent);
                return;
            }

            p.respawnTimer = 60;
        }

        function respawnPlayer(p) {
            p.x = p.spawnX; p.y = p.spawnY; p.vx = 0; p.vy = 0;
            p.isJumping = false; p.attackType = null; p.attackCooldown = 0;
            p.attackAnimTimer = 0; p.stunTimer = 0; p.percent = 0;
            p.isDead = false;
            if (p.mesh) p.mesh.visible = true;
            updateHud();
        }

        function updateHud() {
            document.getElementById('p1-pct').innerText = Math.floor(p1.percent);
            document.getElementById('p2-pct').innerText = Math.floor(p2.percent);
        }

        function showDamagePopup(x, y, damageVal) {
            const vector = new THREE.Vector3(x, y + 0.5, 0).project(camera);
            const popup = document.createElement('div');
            popup.className = 'dmg-popup';
            popup.innerText = '+' + damageVal + '%';
            popup.style.left = ((vector.x * 0.5 + 0.5) * window.innerWidth) + 'px';
            popup.style.top = ((-(vector.y * 0.5 - 0.5) * window.innerHeight)) + 'px';
            document.body.appendChild(popup);
            setTimeout(() => popup.remove(), 600);
        }

        function handleSpecialsAndAttacks(attacker, opponent, upKey, downKey, jumpKey, attackKey, smashKey, specialKey, isCPU = false) {
            if (matchEnded || attacker.isDead || matchIsLocked) return;
            if (attacker.attackCooldown > 0) {
                attacker.attackCooldown--;
                if (attacker.attackAnimTimer > 0) attacker.attackAnimTimer--;
                return;
            }

            let doSpecial = isCPU ? (Math.random() < (cpuLevel * 0.005)) : keys[specialKey];
            let inMeleeRange = Math.abs(attacker.x - opponent.x) <= 2.2 && Math.abs(attacker.y - opponent.y) <= 1.8;
            let doAttack = isCPU ? (inMeleeRange && Math.random() < (cpuLevel * 0.035)) : (keys[smashKey] || keys[attackKey]);

            if (doSpecial) {
                playSfx(sfx.swing);
                attacker.attackCooldown = 45; attacker.attackAnimTimer = 20; attacker.attackType = 'special';
                
                if (attacker.charType === 3) {
                    attacker.x += attacker.facing * 3.5;
                    activeHitboxes.push({ x: attacker.x, y: attacker.y, width: 1.8, height: 1.4, owner: attacker, target: opponent, damage: 11, isSmash: false, lifespan: 8 });
                } else if (attacker.charType === 4) {
                    const pMesh = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.5, 0.5), new THREE.MeshBasicMaterial({ color: 0x00ffcc }));
                    pMesh.position.set(attacker.x + attacker.facing * 0.8, attacker.y, 0);
                    scene.add(pMesh);
                    projectiles.push({ mesh: pMesh, vx: attacker.facing * 0.32, owner: attacker, target: opponent, damage: 12, life: 50 });
                } else if (attacker.charType === 2) {
                    activeHitboxes.push({ x: attacker.x + attacker.facing * 1.2, y: attacker.y, width: 1.6, height: 1.6, owner: attacker, target: opponent, damage: 10, isSmash: true, lifespan: 7 });
                } else {
                    const pMesh = new THREE.Mesh(new THREE.TorusGeometry(0.35, 0.08, 6, 8), new THREE.MeshBasicMaterial({ color: 0x00ff41 }));
                    pMesh.position.set(attacker.x + attacker.facing * 0.8, attacker.y, 0);
                    scene.add(pMesh);
                    projectiles.push({ mesh: pMesh, vx: attacker.facing * 0.28, owner: attacker, target: opponent, damage: 8, life: 45 });
                }
                return;
            }

            if (doAttack) {
                playSfx(sfx.swing);
                const isSmash = isCPU ? (cpuLevel >= 5 && Math.random() < 0.25) : keys[smashKey];
                
                let dmgValue = isSmash ? 13 : 8;
                if (attacker.charType === 3) dmgValue = isSmash ? 14 : 9;
                if (attacker.charType === 4) dmgValue = isSmash ? 15 : 8;

                attacker.attackCooldown = isSmash ? 28 : 18; 
                attacker.attackAnimTimer = isSmash ? 14 : 9; 
                attacker.attackType = isSmash ? 'smash' : 'attack';
                
                activeHitboxes.push({
                    x: attacker.x + attacker.facing * 0.9,
                    y: attacker.y,
                    width: isSmash ? 1.5 : 1.0,
                    height: isSmash ? 1.3 : 0.9,
                    owner: attacker,
                    target: opponent,
                    damage: dmgValue,
                    isSmash: isSmash,
                    lifespan: isSmash ? 8 : 5
                });

            } else if (attacker.attackAnimTimer === 0) {
                attacker.attackType = null;
            }
        }

        function checkGrounded(p) {
            const feetY = p.y - charHalfHeight;
            let bestGround = false;

            for (let plat of platforms) {
                if (!plat || !plat.geometry) continue;

                // Explicit world-space platform bounds.
                const halfW = plat.geometry.parameters.width * 0.5;
                const halfH = plat.geometry.parameters.height * 0.5;
                const left = plat.position.x - halfW;
                const right = plat.position.x + halfW;
                const top = plat.position.y + halfH;

                // Only land when crossing the top surface while falling.
                if (p.x >= left - 0.05 && p.x <= right + 0.05 &&
                    p.vy <= 0 &&
                    feetY <= top + 0.18 &&
                    feetY >= top - 0.55) {
                    if (bestGround === false || top > bestGround) bestGround = top;
                }
            }

            return bestGround === false ? false : bestGround + charHalfHeight;
        }

        function updateCPUAI(cpu, target) {
            if (matchEnded || cpu.stunTimer > 0 || cpu.isDead || matchIsLocked) return;
            
            const diffX = target.x - cpu.x;
            cpu.facing = diffX > 0 ? 1 : -1;

            if (Math.abs(diffX) > 1.2) {
                cpu.vx = Math.sign(diffX) * maxWalkSpeed * (0.4 + (cpuLevel / 10) * 0.6);
            } else {
                cpu.vx *= friction;
            }

            const groundY = checkGrounded(cpu);
            if (groundY !== false && target.y > cpu.y + 1.5 && Math.random() < (cpuLevel * 0.04)) {
                cpu.vy = jumpForce;
                cpu.isJumping = true;
                cpu.y += 0.05;
            }
        }

        function updatePlayerPhysics(p, opponent, leftKey, rightKey, upKey, downKey, jumpKey, attackKey, smashKey, specialKey, isCPU = false) {
            if (matchEnded) return;

            if (p.isDead) {
                if (p.respawnTimer > 0) {
                    p.respawnTimer--;
                    if (p.respawnTimer === 0) {
                        respawnPlayer(p);
                    }
                }
                return;
            }

            if (matchIsLocked) {
                p.vx = 0;
                p.vy = 0;
                return;
            }

            if (isCPU) {
                updateCPUAI(p, opponent);
            }

            handleSpecialsAndAttacks(p, opponent, upKey, downKey, jumpKey, attackKey, smashKey, specialKey, isCPU);
            if (p.stunTimer > 0) p.stunTimer--;

            const groundY = checkGrounded(p);
            if (groundY !== false) {
                p.y = groundY; p.vy = 0; p.isJumping = false;
                if (!isCPU) {
                    if (keys[leftKey]) { p.vx = Math.max(p.vx - groundAccel, -maxWalkSpeed); p.facing = -1; }
                    else if (keys[rightKey]) { p.vx = Math.min(p.vx + groundAccel, maxWalkSpeed); p.facing = 1; }
                    else { p.vx *= friction; if (Math.abs(p.vx) < 0.001) p.vx = 0; }
                } else {
                    p.vx *= friction;
                }

                if ((!isCPU && keys[jumpKey])) { p.vy = jumpForce; p.isJumping = true; p.y += 0.05; }
            } else {
                if (!isCPU) {
                    if (keys[leftKey]) { p.vx = Math.max(p.vx - airMobility, -maxWalkSpeed * 1.15); p.facing = -1; }
                    else if (keys[rightKey]) { p.vx = Math.min(p.vx + airMobility, maxWalkSpeed * 1.15); p.facing = 1; }
                }
                if (p.isJumping && !isCPU && !keys[jumpKey] && p.vy > 0) { p.vy *= shortHopMult; p.isJumping = false; }
                p.vy += gravity;
            }

            p.x += p.vx; p.y += p.vy;

            if (p.x < blastzone.left || p.x > blastzone.right || p.y > blastzone.top || p.y < blastzone.bottom) {
                handleElimination(p, opponent);
            }
        }

        // --- Main Animation Loop ---
        function animate() {
            requestAnimationFrame(animate);
            drawAnimatedBg();

            if (currentGameState !== 'PLAYING') {
                renderer.render(scene, camera);
                return;
            }

            if (finisherTimer > 0) {
                finisherTimer--;
                if (finisherTimer === 0) {
                    document.getElementById('finisher-flash').classList.remove('active');
                }
                [p1, p2].forEach(p => {
                    if (p.mesh && !p.isDead) {
                        p.mesh.position.set(p.x + (Math.random() - 0.5) * 0.45, p.y + (Math.random() - 0.5) * 0.45, (Math.random() - 0.5) * 0.35);
                    }
                });
                renderer.render(scene, camera);
                return;
            }

            if (hitstopFrames > 0 && !matchEnded) {
                hitstopFrames--;
                renderer.render(scene, camera);
                return;
            }

            updatePlayerPhysics(p1, p2, 'KeyA', 'KeyD', 'KeyW', 'KeyS', 'Space', 'KeyF', 'KeyR', 'KeyT', false);
            updatePlayerPhysics(p2, p1, 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'ShiftRight', 'KeyL', 'Slash', 'KeyP', p2IsCPU);

            for (let i = activeHitboxes.length - 1; i >= 0; i--) {
                const hb = activeHitboxes[i];
                hb.lifespan--;

                if (hb.target.isDead) {
                    activeHitboxes.splice(i, 1);
                    continue;
                }

                const targetBox = { minX: hb.target.x - 0.6, maxX: hb.target.x + 0.6, minY: hb.target.y - 0.8, maxY: hb.target.y + 0.8 };
                const hitboxBounds = { minX: hb.x - hb.width / 2, maxX: hb.x + hb.width / 2, minY: hb.y - hb.height / 2, maxY: hb.y + hb.height / 2 };

                if (hitboxBounds.minX < targetBox.maxX && hitboxBounds.maxX > targetBox.minX &&
                    hitboxBounds.minY < targetBox.maxY && hitboxBounds.maxY > targetBox.minY) {
                    
                    const isCritFinisher = hb.isSmash && hb.target.percent >= 120 && hb.target.stocks === 1;

                    if (isCritFinisher) {
                        playSfx(sfx.crit);
                    } else {
                        playSfx(sfx.hit);
                    }

                    hb.target.percent += hb.damage;
                    updateHud();
                    showDamagePopup(hb.target.x, hb.target.y, hb.damage);
                    spawnHitSparks(hb.target.x, hb.target.y, hb.isSmash);

                    if (isCritFinisher) {
                        finisherTimer = 48; // 0.8 seconds at 60 FPS
                        document.getElementById('finisher-flash').classList.add('active');
                    }

                    const percentMultiplier = hb.target.percent * 0.0038;
                    const baseKb = hb.isSmash ? (hb.target.percent >= 120 ? 0.38 : 0.16) : 0.07;
                    const finalKb = baseKb * (1.0 + percentMultiplier * 2.2);

                    hb.target.vx = hb.owner.facing * finalKb;
                    hb.target.vy = finalKb * (hb.isSmash && hb.target.percent >= 120 ? 1.4 : 0.9);
                    hb.target.stunTimer = hb.isSmash ? Math.min(35, 15 + Math.floor(hb.target.percent * 0.18)) : 10;
                    
                    // Random static rotation on hit with no keyframes/recovery
                    hb.target.hitRandomRotX = (Math.random() - 0.5) * 3.5;
                    hb.target.hitRandomRotY = (Math.random() - 0.5) * 3.5;
                    hb.target.hitRandomRotZ = (Math.random() - 0.5) * 4.0;

                    hitstopFrames = hb.isSmash ? 9 : 3;

                    activeHitboxes.splice(i, 1);
                    continue;
                }

                if (hb.lifespan <= 0) {
                    activeHitboxes.splice(i, 1);
                }
            }

            for (let i = projectiles.length - 1; i >= 0; i--) {
                const pr = projectiles[i];
                pr.mesh.position.x += pr.vx; pr.life--;
                if (!pr.target.isDead && Math.abs(pr.mesh.position.x - pr.target.x) < 1.2 && Math.abs(pr.mesh.position.y - pr.target.y) < 1.2) {
                    playSfx(sfx.hit);
                    pr.target.percent += pr.damage; pr.target.vx = Math.sign(pr.vx) * 0.12; pr.target.vy = 0.08; pr.target.stunTimer = 12;
                    pr.target.hitRandomRotX = (Math.random() - 0.5) * 3.0;
                    pr.target.hitRandomRotY = (Math.random() - 0.5) * 3.0;
                    pr.target.hitRandomRotZ = (Math.random() - 0.5) * 3.5;
                    updateHud(); showDamagePopup(pr.target.x, pr.target.y, pr.damage); spawnHitSparks(pr.target.x, pr.target.y);
                    scene.remove(pr.mesh); projectiles.splice(i, 1); continue;
                }
                if (pr.life <= 0) { scene.remove(pr.mesh); projectiles.splice(i, 1); }
            }

            for (let i = hitSparks.length - 1; i >= 0; i--) {
                const s = hitSparks[i];
                s.mesh.position.x += s.vx; s.mesh.position.y += s.vy; s.life--;
                if (s.life <= 0) { scene.remove(s.mesh); hitSparks.splice(i, 1); }
            }

            // Abstract Dynamic Tilt & Squash Animation Variants
            [p1, p2].forEach(p => {
                if (p.mesh && !p.isDead) {
                    const isStunned = p.stunTimer > 0;
                    const activeColor = isStunned ? WHITE_FLASH : (Math.random() < 0.1 ? DARK_GREEN : BASE_GREEN);
                    
                    p.mesh.children.forEach(child => {
                        if (child.material) child.material.color.setHex(activeColor);
                    });

                    p.mesh.position.set(p.x, p.y, 0);

                    if (isStunned) {
                        // Apply static random hit rotation instantly with no keyframed interpolation
                        p.mesh.rotation.set(p.hitRandomRotX, p.hitRandomRotY, p.hitRandomRotZ);
                        p.mesh.scale.set(1.0, 1.0, 1.0);
                    } else if (p.attackAnimTimer > 0) {
                        // Dynamic unique attack distortion variants depending on attack type
                        const attackProgress = p.attackAnimTimer;
                        if (p.attackType === 'smash') {
                            p.mesh.rotation.set(0, p.facing === 1 ? 0 : Math.PI, p.facing * -0.9);
                            p.mesh.scale.set(1.4, 0.7, 1.2);
                        } else if (p.attackType === 'special') {
                            p.mesh.rotation.set(0, p.facing === 1 ? 0 : Math.PI, (attackProgress * 0.5) * p.facing);
                            p.mesh.scale.set(1.1, 1.3, 0.9);
                        } else {
                            p.mesh.rotation.set(0, p.facing === 1 ? 0 : Math.PI, p.facing * -0.45);
                            p.mesh.scale.set(1.25, 0.85, 1.0);
                        }
                    } else {
                        // Normal movement / idle squash and tilt based on velocity direction
                        let moveTilt = p.vx * 1.5 * p.facing;
                        let stretchFactor = 1.0 + Math.abs(p.vx) * 0.4;
                        let squashFactor = 1.0 - Math.abs(p.vx) * 0.2;
                        
                        p.mesh.rotation.set(0, p.facing === 1 ? 0 : Math.PI, moveTilt);
                        p.mesh.scale.set(stretchFactor, squashFactor, 1.0);
                    }
                }
            });

            // Camera tracking
            if (p1.mesh && p2.mesh) {
                const midX = (p1.x + p2.x) / 2;
                const midY = Math.max((p1.y + p2.y) / 2 + 0.5, 0);
                const distance = Math.abs(p1.x - p2.x);
                
                let zoomFactor = finisherTimer > 0 ? 0.025 : 0.85; 
                let targetZ = finisherTimer > 0 ? 2.8 : (6 + Math.max(distance, 6) * zoomFactor);
                
                let shakeX = 0;
                let shakeY = 0;
                if (finisherTimer > 0) {
                    shakeX = (Math.random() - 0.5) * 0.25;
                    shakeY = (Math.random() - 0.5) * 0.25;
                }

                camTargetPos.set(midX + shakeX, midY + shakeY, targetZ);
                camera.position.lerp(camTargetPos, finisherTimer > 0 ? 0.4 : 0.1);
                camera.lookAt(midX + shakeX, midY + shakeY, 0);
            }

            renderer.render(scene, camera);
        }

        animate();

        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(Math.max(window.devicePixelRatio / pixelScaleFactor, 0.5));
            columns = Math.floor(window.innerWidth / fontSize);
            drops = Array(columns).fill(1);
        });
    </script>
</body>
</html>

Game Source: Faulty Coding: Nega 64

Creator: FlashGalaxy17

Libraries: three

Complexity: complex (1631 lines, 75.3 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: faulty-coding-nega-64-flashgalaxy17" to link back to the original. Then publish at arcadelab.ai/publish.