/**
 * qix_game_main.js
 * ゲームのメインループと状態管理
 * (演出高速化・ランダム配置・完全版)
 */

class Game {
    constructor() {
        this.canvas = document.getElementById('gameCanvas');
        this.grid = new GridSystem();
        this.renderer = new Renderer(this.canvas);
        this.assets = new AssetManager();
        this.stageIndex = 0;
        this.player = { x: 0, y: 0, visible: true };
        this.currentDir = { x: 0, y: 0 };
        this.lastDir = { x: 0, y: -1 }; 
        this.safePos = { x: 0, y: 0 };
        this.deadPos = { x: 0, y: 0 }; 

        this.input = { x: 0, y: 0 };
        this.keyState = { ArrowUp: false, ArrowDown: false, ArrowLeft: false, ArrowRight: false };

        this.turnQueue = null;
        this.enemies = [];
        this.projectiles = [];
        this.trailPoints = [];
        this.claimEffects = [];
        this.voidEntryTimer = 0;
        this.VOID_ENTRY_THRESHOLD = 15;
        this.claimedCount = 0;
        this.totalCount = 0;
        
        this.state = 'TITLE'; 
        
        this.menuIndex = 0;
        this.menuItems = ['FREE', 'STAGE CREATE', 'GALLERY', 'HOW TO PLAY', 'ACHIEVEMENT', 'OPTION'];

        this.currentStageList = [];

        this.mp = CONFIG.MAX_MP;
        this.boostStock = 1;
        this.isBoosting = false;
        
        this.isConcentrating = false;
        this.frameCount = 0;
        this.CONCENTRATE_COST_PER_FRAME = 0.15; 
        this.DIAGONAL_COST_FACTOR = 1.41; 

        // 演出用タイマー・パラメータ
        this.freezeTimer = 0;
        this.pendingCost = 0;
        this.rewindWaitTimer = 0;
        this.explosionTimer = 0;
        this.clearWaitTimer = 0;
        this.timeStopTimer = 0;
        this.enemySlowTimer = 0;
        this.enemySlowMult = 1.0;

        this.particles = [];
        
        this.erosionRipples = []; 
        this.introViewTimer = 0; 
        this.seepAlpha = 0;
        
        this.introTimer = 0;
        this.overlayAlpha = 0;
        this.currentOverlayAlpha = 0; 

        this.sparksConfig = null;
        this.sparksSpawned = false;
        
        this.shakeTimer = 0;
        this.damageCooldown = 0;
        this.msgPriority = 0;

        this.trailSoundSource = null;
        this.needsBgUpdate = false; // snake侵食などでの再描画フラグ

        this._padBoostPrev  = false;
        this._padSlowPrev   = false;
        this._padPausePrev  = false;
        this._padNavPrev    = { up: false, down: false, left: false, right: false, a: false, b: false };

        this.options = { bgmVol: 70, sfxVol: 70, stickDead: 16, turnDelay: 4 };
        this.loadOptions();

        this.audio = new AudioManager();
        this.audio.setBgmVolume(this.options.bgmVol);
        this.audio.setSfxVolume(this.options.sfxVol);

        this.bindInput();
    }

    loadOptions() {
        try {
            const saved = localStorage.getItem('qix_options');
            if (saved) Object.assign(this.options, JSON.parse(saved));
        } catch(e) {}
    }

    saveOptions() {
        localStorage.setItem('qix_options', JSON.stringify(this.options));
    }

    resetAllData() {
        if (!confirm('実績・統計・カスタムステージを全て削除します。よろしいですか？')) return;
        ['qix_achievements','qix_stats','qix_custom_free_stages','qix_options'].forEach(k => localStorage.removeItem(k));
        location.reload();
    }

    calculateRisk() {
        let risk = 0;
        for(let p of this.trailPoints) {
            risk += (p.cost || 1) * CONFIG.COST_PER_PIXEL;
        }
        return Math.floor(risk);
    }

    returnToTitle() {
        this.stopTrailSound();
        this.audio.stopBGM();
        this.state = 'TITLE';
        
        this.ui.hideAll(); 
        document.getElementById('title-overlay').style.display = 'flex';
        
        this.menuIndex = 0;
        this.updateMenuVisual();
        
        this.setupTitleMouseEvents();
    }

    setupTitleMouseEvents() {
        const items = document.querySelectorAll('.menu-item');
        items.forEach((item, index) => {
            item.onmouseenter = () => {
                this.menuIndex = index;
                this.updateMenuVisual();
            };
            item.onclick = () => {
                this.menuIndex = index;
                this.updateMenuVisual();
                this.executeMenuAction();
            };
        });
    }

    updateMenuVisual() {
        const items = document.querySelectorAll('.menu-item');
        items.forEach((item, idx) => {
            if (idx === this.menuIndex) item.classList.add('selected');
            else item.classList.remove('selected');
        });
    }

    executeMenuAction() {
        const mode = this.menuItems[this.menuIndex];

        switch (mode) {
            case 'FREE':
                this.currentStageList = STAGE_DATA.FREE;
                this.state = 'MENU';
                this.ui.hideAll();
                this.ui.showStageSelect(false);
                break;
            case 'STAGE CREATE':
                document.getElementById('title-overlay').style.display = 'none';
                this.creator.open();
                break;
            case 'GALLERY':
                document.getElementById('title-overlay').style.display = 'none';
                this.ui.showGallery();
                break;
            case 'HOW TO PLAY':
                document.getElementById('title-overlay').style.display = 'none';
                this.ui.showHowToPlay();
                break;
            case 'ACHIEVEMENT':
                document.getElementById('title-overlay').style.display = 'none';
                this.ui.showAchievements();
                break;
            case 'OPTION':
                document.getElementById('title-overlay').style.display = 'none';
                this.ui.showOptions();
                break;
        }
    }

    showSubScreen(title, content) {
        document.getElementById('title-overlay').style.display = 'none';
        const sub = document.getElementById('sub-overlay');
        sub.style.display = 'flex';
        document.getElementById('sub-title').innerText = title;
        document.getElementById('sub-content').innerHTML = content;
    }

    returnToMenu() {
        this.stopTrailSound();
        this.audio.stopBGM();
        this.state = 'MENU';
        this.ui.showStageSelect();
    }

    pauseGame() {
        this._handleEscape();
    }

    _handleEscape() {
        const pauseEl = document.getElementById('pause-overlay');
        // 中断確認中ならキャンセル
        if (pauseEl.style.display !== 'none') {
            this._cancelAbort();
            return;
        }
        // ゲームプレイ中 → 中断確認
        const playingStates = new Set(['PLAYING','REWIND','EXPLODING','FREEZE','INTRO_VIEW','INTRO_EROSION_RIPPLE','INTRO_START','VOICE_START']);
        if (playingStates.has(this.state)) {
            this._pauseFocusIndex = 0;
            this._updatePauseFocus();
            pauseEl.style.display = 'flex';
            return;
        }
        // ステージ選択 → タイトルへ
        if (this.state === 'MENU') {
            this.returnToTitle();
            return;
        }
        // タイトル画面でサブ画面が出ている → タイトルに戻す
        if (this.state === 'TITLE') {
            const subScreens = ['achievements-overlay','options-overlay','howtoplay-overlay','sub-overlay'];
            const anyOpen = subScreens.some(id => document.getElementById(id).style.display !== 'none');
            if (anyOpen) this.returnToTitle();
            return;
        }
    }

    _updatePauseFocus() {
        document.getElementById('pause-ok').classList.toggle('focused', this._pauseFocusIndex === 0);
        document.getElementById('pause-cancel').classList.toggle('focused', this._pauseFocusIndex === 1);
    }

    _confirmAbort() {
        document.getElementById('pause-overlay').style.display = 'none';
        this.returnToMenu();
    }

    _cancelAbort() {
        document.getElementById('pause-overlay').style.display = 'none';
    }

    showMessage(text, duration, color = '#FFFFFF', priority = 0) {
        if (this.msgPriority > priority) return;
        this.msgPriority = priority;
        const msg = document.getElementById('message');
        msg.innerHTML = text; 
        msg.style.color = color;
        
        if (duration > 3000) {
            msg.className = "msg-pop-long"; 
        } else {
            msg.className = "msg-pop";
        }
        
        setTimeout(() => { 
            if(msg.className.includes("msg-pop")) { 
                msg.className = ""; 
                this.msgPriority = 0; 
            } 
        }, duration + 500);
    }

    updateUI(currentRisk = null, instant = false) {
        if (this.achievements) this.achievements.onMpUpdate(this.mp);
        const percent = Math.floor((this.claimedCount / this.totalCount) * 100);
        document.getElementById('score').innerText = `ERODED: ${percent}%`;
        
        const mpPercent = Math.max(0, (this.mp / CONFIG.MAX_MP) * 100);
        const mpBar = document.getElementById('mp-bar');
        
        if (instant) {
            mpBar.classList.add('no-transition');
        } else {
            mpBar.classList.remove('no-transition');
        }
        
        mpBar.style.width = `${mpPercent}%`;
        
        let riskPercent = 0;
        const isRewinding = (this.state === 'FREEZE' || this.state === 'REWIND' || this.state === 'FREEZE_END');
        
        if (!isRewinding) {
            if (currentRisk === null) {
                currentRisk = this.calculateRisk(); 
            }
            if (this.mp > 0) riskPercent = (currentRisk / this.mp) * 100;
        }
        
        document.getElementById('risk-bar').style.width = `${riskPercent}%`;
        document.getElementById('boost-stock').innerText = `💎 x${this.boostStock}`;
    }

    async init() {
        await this.audio.loadAll();
        // エディタからlocalStorageに保存されたカスタムステージを読み込む
        try {
            const free = localStorage.getItem('qix_custom_free_stages');
            if (free) {
                const extra = JSON.parse(free).filter(s => typeof s.id === 'number' && s.id > 100);
                if (extra.length > 0)
                    STAGE_DATA.FREE = [...STAGE_DATA.FREE.filter(s => s.id <= 100), ...extra];
            }
        } catch (e) {
            console.warn('[Editor] カスタムFREEステージの読み込みに失敗:', e);
        }
        StageCreator.mergeCreatorSlots();
        this.ui = new UIManager(this);
        this.achievements = new AchievementSystem();
        this.creator = new StageCreator(this);
        this._isCustomStage = false;
        this.returnToTitle();
        this.loop();
    }

    async loadStage(index) {
        this.state = 'LOADING';
        this.stageIndex = index;
        
        if (!this.currentStageList || this.currentStageList.length === 0) {
            this.currentStageList = STAGE_DATA.FREE;
        }
        const data = this.currentStageList[index];

        const _sn = data.name || data._stageName || '';
        document.getElementById('stage-info').innerText = `STAGE ${String(data.id).padStart(2, '0')}` + (_sn ? `  「${_sn}」` : '');

        const msg = document.getElementById('message');
        msg.className = '';
        msg.style.display = 'none';
        this.msgPriority = 0;
        
        document.body.classList.remove('rewind-mode');
        document.body.classList.remove('color-recover');
        document.body.classList.remove('danger-mode');
        document.body.classList.remove('time-stop-mode');
        document.body.classList.remove('concentrate-mode');

        document.getElementById('ui').style.opacity = '1';
        document.getElementById('ui').style.display = 'flex';
        document.getElementById('ui-top-row').style.display = 'flex';
        document.getElementById('controls').style.opacity = '1';
        document.getElementById('controls').style.display = 'flex';

        if (this.audio) { this.audio.stopBgm(); this.audio.startBgm(); }

        const imgHidden = await this.assets.loadImage('hidden', data.assets.hidden);
        const imgReveal = await this.assets.loadImage('reveal', data.assets.reveal);

        this.grid.reset();
        this.totalCount = (this.grid.cols - 2) * (this.grid.rows - 2);
        this.claimedCount = 0;
        this.trailPoints = [];
        this.particles = [];
        this.projectiles = [];
        this.claimEffects = [];
        
        this.erosionRipples = [];
        this.introViewTimer = 0;
        this.seepAlpha = 0;
        this.timeStopTimer = 0;
        this.enemySlowTimer = 0;
        this.enemySlowMult = 1.0;
        this.shakeTimer = 0;
        this.damageCooldown = 0;
        this.isConcentrating = false;
        this.frameCount = 0;
        this.pendingCost = 0;

        this.overlayAlpha = (data.overlayAlpha !== undefined) ? data.overlayAlpha : 0.6;
        this.currentOverlayAlpha = 0; 

        this.renderer.updateBackground(this.grid, { hidden: imgHidden, reveal: imgReveal }, this.overlayAlpha);

        this.player.x = Math.floor(this.grid.cols / 2);
        this.player.y = this.grid.rows - 1;
        this.player.visible = true; 
        this.safePos = { x: this.player.x, y: this.player.y };

        this.currentDir = { x: 0, y: 0 };
        this.lastDir = { x: 0, y: -1 }; 
        this.input = { x: 0, y: 0 };
        this.turnQueue = null;
        this.voidEntryTimer = 0;

        this.mp = CONFIG.MAX_MP;
        this.boostStock = 1;
        this.isBoosting = false;

        // 実績フック: ステージ開始
        if (this.achievements) this.achievements.startStage(data);

        this.enemies = data.enemies.map(e => {
            if (e.type === 'SHOOTER')   return new ShooterEnemy(e);
            if (e.type === 'TRACKER')   return new TrackerEnemy(e, this.player);
            if (e.type === 'BEAM_BOSS')   return new BeamBossEnemy(e, this.player);
            if (e.type === 'OCTA_BEAM')   return new OctaBeamEnemy(e, this.player);
            if (e.type === 'SNIPER_BEAM') return new SniperBeamEnemy(e, this.player);
            if (e.type === 'SNAKE')       return new SnakeEnemy(e, this.player);
            if (e.type === 'MINE')      return new MineEnemy(e);
            if (e.type === 'GHOST')     return new GhostEnemy(e, this.player);
            return new QixEnemy(e);
        });
        
        this.sparksConfig = data.sparks || null;
        this.sparksSpawned = false;

        this.updateUI();
        
        document.getElementById('dialogue-overlay').style.display = 'none';
        this.dialogueLines = [];
        this.dialogueIndex = 0;
        this.dialogueCallback = null;

        this.state = 'INTRO_VIEW';
        this.introViewTimer = 0;
    }

    showDialogue(lines, callback) {
        this.dialogueLines = lines || [];
        this.dialogueIndex = 0;
        this.dialogueCallback = callback;
        if (this.dialogueLines.length === 0) {
            if (callback) callback();
            return;
        }
        this.state = 'DIALOGUE';
        this._renderDialogue();
    }

    _renderDialogue() {
        const line = this.dialogueLines[this.dialogueIndex];
        const overlay = document.getElementById('dialogue-overlay');
        document.getElementById('dialogue-speaker').innerText = line.speaker || '';
        document.getElementById('dialogue-text').innerText = line.text || '';
        overlay.style.display = 'block';
    }

    advanceDialogue() {
        this.dialogueIndex++;
        if (this.dialogueIndex < this.dialogueLines.length) {
            this._renderDialogue();
        } else {
            document.getElementById('dialogue-overlay').style.display = 'none';
            const cb = this.dialogueCallback;
            this.dialogueCallback = null;
            if (cb) cb();
        }
    }


    // ==========================================
    // メインループ更新
    // ==========================================

    _pollGamepad() {
        const pads = navigator.getGamepads ? navigator.getGamepads() : [];
        const pad  = pads[0];
        if (!pad) return;

        const DEAD = 0.25;
        const ax   = pad.axes[0] || 0;
        const ay   = pad.axes[1] || 0;

        // スティック → 移動入力（PLAYING時のみ）
        if (this.state === 'PLAYING') {
            const absX = Math.abs(ax), absY = Math.abs(ay);
            if (absX > DEAD || absY > DEAD) {
                const DIAG = 0.35;
                const ratio = (absX > DEAD && absY > DEAD) ? Math.min(absX, absY) / Math.max(absX, absY) : 0;
                if (ratio >= DIAG) {
                    this.input = { x: ax > 0 ? 1 : -1, y: ay > 0 ? 1 : -1 };
                } else {
                    this.input = absX >= absY
                        ? { x: ax > 0 ? 1 : -1, y: 0 }
                        : { x: 0, y: ay > 0 ? 1 : -1 };
                }
            } else {
                // Dpad
                const dp = pad.buttons;
                const du = dp[12]?.pressed, dd = dp[13]?.pressed, dl = dp[14]?.pressed, dr = dp[15]?.pressed;
                if (du || dd || dl || dr) {
                    this.input = { x: dr ? 1 : dl ? -1 : 0, y: dd ? 1 : du ? -1 : 0 };
                } else {
                    this.input = { x: 0, y: 0 };
                }
            }
        }

        const btn = pad.buttons;
        // SLOW: LT(6) or LB(4)
        const slowNow = btn[6]?.pressed || btn[4]?.pressed;
        if (slowNow && !this._padSlowPrev && this.state === 'PLAYING') {
            if (this.mp > 0 && !this.isConcentrating) {
                this.isConcentrating = true;
                this.isBoosting = false;
                document.body.classList.add('concentrate-mode');
                if (this.audio) this.audio.playSlowOn();
            }
        }
        if (!slowNow && this._padSlowPrev) {
            if (this.isConcentrating && this.audio) this.audio.playSlowOff();
            this.isConcentrating = false;
            document.body.classList.remove('concentrate-mode');
        }
        this._padSlowPrev = slowNow;

        // BOOST: A(0) or X(2)
        const boostNow = btn[0]?.pressed || btn[2]?.pressed;
        if (boostNow && !this._padBoostPrev && this.state === 'PLAYING') {
            this.activateBoost();
        }
        this._padBoostPrev = boostNow;

        // START(9) / OPTIONS(9) → ESC相当
        const pauseNow = btn[9]?.pressed;
        if (pauseNow && !this._padPausePrev) this._handleEscape();
        this._padPausePrev = pauseNow;

        // メニュー画面ナビ (MENU / GAMEOVER)
        const nav = {
            up:    btn[12]?.pressed || ay < -DEAD,
            down:  btn[13]?.pressed || ay >  DEAD,
            left:  btn[14]?.pressed || ax < -DEAD,
            right: btn[15]?.pressed || ax >  DEAD,
            a:     btn[0]?.pressed,
            b:     btn[1]?.pressed,
        };
        const prev = this._padNavPrev;
        if (this.state === 'MENU') {
            if (nav.right && !prev.right) this.ui.stageSelectKey('ArrowRight');
            if (nav.left  && !prev.left)  this.ui.stageSelectKey('ArrowLeft');
            if (nav.down  && !prev.down)  this.ui.stageSelectKey('ArrowDown');
            if (nav.up    && !prev.up)    this.ui.stageSelectKey('ArrowUp');
            if (nav.a     && !prev.a)     this.ui.stageSelectKey('Enter');
            if (nav.b     && !prev.b)     this.ui.stageSelectKey('Escape');
        }
        if (this.state === 'GAMEOVER') {
            if ((nav.left || nav.right) && !(prev.left || prev.right)) this.ui.gameOverKey('ArrowLeft');
            if (nav.a && !prev.a) this.ui.gameOverKey('Enter');
        }
        this._padNavPrev = { ...nav };
    }

    update() {
        this._pollGamepad();
        this.frameCount++;

        if (this.isConcentrating) {
            this.mp -= this.CONCENTRATE_COST_PER_FRAME;
            if (this.mp <= 0) {
                this.mp = 0;
                this.isConcentrating = false;
                document.body.classList.remove('concentrate-mode');
            }
        }

        if (this.isConcentrating && this.frameCount % 2 !== 0) {
            this.updateUI(this.calculateRisk());
            return;
        }

        this.particles = this.particles.filter(p => p.life > 0);
        this.particles.forEach(p => p.update());
        this.claimEffects = this.claimEffects.filter(e => e.life > 0);
        this.claimEffects.forEach(e => e.update());

        if (this.shakeTimer > 0) this.shakeTimer--;
        if (this.damageCooldown > 0) this.damageCooldown--;

        // --- 状態ごとの処理 ---

        if (this.state === 'INTRO_VIEW') {
            this.currentOverlayAlpha = 0;
            const data = this.currentStageList[this.stageIndex];
            this.introViewTimer++;

            if (this.introViewTimer > 60) {
                const startLines = (data.dialogue && data.dialogue.start) ? data.dialogue.start : [];
                if (startLines.length > 0) {
                    this.showDialogue(startLines, () => {
                        this.state = 'INTRO_EROSION_RIPPLE';
                        this.erosionRipples = [];
                        this.frameCount = 0;
                    });
                } else {
                    this.state = 'INTRO_EROSION_RIPPLE';
                    this.erosionRipples = [];
                    this.frameCount = 0;
                }
            }
            return;
        }

        if (this.state === 'DIALOGUE') {
            return;
        }

        // ★修正: パパパパッと高速展開する波紋演出
        if (this.state === 'INTRO_EROSION_RIPPLE') { 
            const MAX_RIPPLES = 12; // 波紋の数を多めに
            const RIPPLE_SPEED = 12; // 広がる速度を速く
            
            // 6フレームごと(0.1秒)に波紋発生
            if (this.frameCount % 6 === 0 && this.erosionRipples.length < MAX_RIPPLES) {
                // 画面全体にランダム配置
                const x = Math.random() * CONFIG.CANVAS_SIZE;
                const y = Math.random() * CONFIG.CANVAS_SIZE;

                this.erosionRipples.push({
                    x: x, y: y, radius: 0,
                    maxRadius: CONFIG.CANVAS_SIZE * 1.5, // 画面端まで確実に覆う
                    speed: RIPPLE_SPEED + Math.random() * 5
                });
            }

            // 波紋更新
            let covered = true; // 画面全体が覆われたか？
            this.erosionRipples.forEach(ripple => {
                ripple.radius += ripple.speed;
                // まだ小さい波紋があれば覆われていないとみなす（簡易判定）
                if (ripple.radius < CONFIG.CANVAS_SIZE * 1.5) covered = false; 
            });
            
            // 全ての波紋が発生し、かつある程度広がったら終了
            if (this.erosionRipples.length >= MAX_RIPPLES && covered) { 
                this.state = 'INTRO_APPEAR'; 
                this.introTimer = 0; 
            }
            return; 
        }

        if (this.state === 'INTRO_APPEAR') { 
            this.introTimer += 0.05; 
            if (this.introTimer >= 1.0) { 
                this.introTimer = 1.0; 
                this.state = 'INTRO_START';
                this.showMessage("EROSION START", 1000, '#BC13FE', 1);
                this.audio.play('se_boost', 0.5);
                setTimeout(() => {
                    if(this.state === 'INTRO_START') {
                        this.state = 'PLAYING';
                        this.audio.playBGM('bgm_main', 0.6);
                    }
                }, 1000);
            } 
            return; 
        }
        if (this.state === 'INTRO_START') return;

        if (this.state === 'CLEAR_ANIM') {
            // claimEffectsは上部の無条件updateで消費される
            if (this.claimEffects.length === 0) {
                this.state = 'CLEAR_SEEP';
                this.seepAlpha = 0;
            }
            return;
        }

        if (this.state === 'CLEAR_SEEP') {
            this.seepAlpha += 0.02;
            if (this.seepAlpha >= 1.0) {
                this.seepAlpha = 1.0;
                this.startClearWait();
            }
            return;
        }
        
        if (this.state === 'CLEAR_WAIT') {
            this.clearWaitTimer--;
            this.seepAlpha = Math.max(0, this.seepAlpha - 0.01);
            if (this.clearWaitTimer <= 0) {
                const data = this.currentStageList[this.stageIndex];
                const endLines = (data.dialogue && data.dialogue.end) ? data.dialogue.end : [];
                if (endLines.length > 0) {
                    this.showDialogue(endLines, () => { this.nextStage(); });
                } else {
                    this.nextStage();
                }
            }
            return;
        }

        if (this.state === 'EXPLODING') {
            this.explosionTimer--;
            if (this.explosionTimer <= 0) {
                if (this.mp < this.pendingCost) {
                    this.gameOver();
                } else {
                    this.startFreeze();
                }
            }
            return;
        }

        if (this.state === 'FREEZE') {
            this.createGatheringParticles(this.player.x, this.player.y, '#00FFFF');
            this.freezeTimer--;
            if (this.freezeTimer <= 0) {
                this.state = 'REWIND';
                document.body.classList.add('rewind-mode'); 
                if (this.trailPoints.length > 0) {
                    this.player.visible = true;
                    this.createExplosion(this.player.x, this.player.y, '#00FFFF');
                    this.audio.play('se_boost', 0.8);
                }
                if (this.trailPoints.length === 0) {
                    this.rewindWaitTimer = 20; 
                } else {
                    this.rewindWaitTimer = 0;
                }
            }
            return;
        }
        
        if (this.state === 'FREEZE_END') {
            this.freezeTimer--;
            if (this.freezeTimer <= 0) {
                this.updateUI();
                this.state = 'PLAYING';
                this.audio.resumeBGM();
                this.player.visible = true;
                document.body.classList.remove('color-recover');
                this.damageCooldown = 60;
            }
            return;
        }

        if (this.state === 'REWIND') {
            this.updateRewind();
            return;
        }

        if (this.state !== 'PLAYING') return;

        // --- ゲームプレイ中 ---

        if (this.sparksConfig && !this.sparksSpawned) {
            const percent = this.claimedCount / this.totalCount;
            if (percent >= 0.4) {
                this.spawnSparks();
            }
        }

        let isTimeStopped = false;
        if (this.timeStopTimer > 0) {
            this.timeStopTimer--;
            isTimeStopped = true;
            document.body.classList.add('time-stop-mode');
        } else {
            document.body.classList.remove('time-stop-mode');
        }

        if (this.enemySlowTimer > 0) {
            this.enemySlowTimer--;
            if (this.enemySlowTimer === 0) this.enemySlowMult = 1.0;
        }
        const slowMult = (this.enemySlowTimer > 0) ? this.enemySlowMult : 1.0;

        const currentRisk = this.calculateRisk();

        if (currentRisk >= this.mp) {
            document.body.classList.add('danger-mode');
        } else {
            document.body.classList.remove('danger-mode');
        }

        let speedMultiplier = 3;
        if (this.isBoosting) speedMultiplier = Math.floor(speedMultiplier * CONFIG.BOOST_SPEED_MULTI);

        let movedThisFrame = false;

        for (let i = 0; i < speedMultiplier; i++) {
            if (this.movePlayer()) {
                movedThisFrame = true;
                break;
            }
            if (this.state !== 'PLAYING') break;
        }

        // チャージ中ビーム予告線を収集 → スネークに注入（回避AI用）
        const _beamWarnings = [];
        const _beamEnemySet = new Set(['BEAM_BOSS', 'OCTA_BEAM', 'SNIPER_BEAM']);
        const C = CONFIG.CANVAS_SIZE;
        for (const e of this.enemies) {
            if (!_beamEnemySet.has(e.type) || e.state !== 'CHARGE') continue;
            if (e.type === 'OCTA_BEAM' && e._dirs8) {
                e._dirs8.forEach(d => _beamWarnings.push({
                    x1: e.x, y1: e.y,
                    x2: Math.max(0, Math.min(C, e.x + d.x * C * 2)),
                    y2: Math.max(0, Math.min(C, e.y + d.y * C * 2))
                }));
            } else if (e.beamDir) {
                _beamWarnings.push({
                    x1: e.x, y1: e.y,
                    x2: Math.max(0, Math.min(C, e.x + e.beamDir.x * C * 2)),
                    y2: Math.max(0, Math.min(C, e.y + e.beamDir.y * C * 2))
                });
            }
        }
        for (const e of this.enemies) {
            if (e.type === 'SNAKE') e.beamWarnings = _beamWarnings;
        }

        for (let i = 0; i < this.enemies.length; i++) {
            let e = this.enemies[i];
            if (!e.active) {
                this.createExplosion(e.x, e.y, e.color);
                this.enemies.splice(i, 1);
                i--;
                continue;
            }

            const result = e.update(this.grid, this.trailPoints, isTimeStopped, slowMult);

            // SnakeがWALLを侵食した場合: claimedCount減少 & 即時背景更新
            if (result && result.erased && result.erased.length > 0) {
                this.claimedCount = Math.max(0, this.claimedCount - result.erased.length);
                this.needsBgUpdate = true; // 同フレーム末でupdateBackgroundを走らせる
            }

            if (e.type === 'SHOOTER' && e.pendingProjectiles.length > 0) {
                e.pendingProjectiles.forEach(req => {
                    if (req.mode === 'BURST') {
                        for(let k=0; k<8; k++) {
                            const ang = k * (Math.PI/4);
                            const speed = 2.0;
                            const vx = Math.cos(ang) * speed;
                            const vy = Math.sin(ang) * speed;
                            this.projectiles.push(new Projectile(req.x, req.y, vx, vy, 6, req.color, 0));
                        }
                    } else {
                        const dx = this.player.x - req.x;
                        const dy = this.player.y - req.y;
                        const baseAngle = Math.atan2(dy, dx);
                        const speed = 6.0;
                        for(let k=-1; k<=1; k++) {
                            const ang = baseAngle + k * 0.15;
                            const vx = Math.cos(ang) * speed;
                            const vy = Math.sin(ang) * speed;
                            this.projectiles.push(new Projectile(req.x, req.y, vx, vy, 5, '#FFFF00', 30));
                        }
                    }
                });
                e.pendingProjectiles = [];
            }

            if (result && result.hit) {
                this.resolveCollision(e.type, result.type, e);
                if (this.state !== 'PLAYING') return;
            }
        }

        if (!isTimeStopped) {
            for (let i = 0; i < this.projectiles.length; i++) {
                let p = this.projectiles[i];
                if (!p.active) {
                    this.projectiles.splice(i, 1);
                    i--;
                    continue;
                }
                
                const res = p.update(this.grid);
                if (res) {
                    if (res.hitWall) {
                        this.createExplosion(p.x, p.y, p.color);
                    } else if (res.hitTrail) {
                        this.resolveCollision('PROJECTILE', 'TRAIL', p);
                        if (this.state !== 'PLAYING') return;
                    }
                }

                if (!p.active) {
                    this.projectiles.splice(i, 1);
                    i--;
                    continue;
                }

                const dx = this.player.x - p.x;
                const dy = this.player.y - p.y;
                if (Math.sqrt(dx*dx + dy*dy) < 6 + p.size) {
                    p.active = false;
                    this.createExplosion(p.x, p.y, p.color);
                    this.resolveCollision('PROJECTILE', 'BODY', p);
                    if (this.state !== 'PLAYING') return;
                }

            }
        }

        // MINEの近接爆発判定（IDLE→WARNING→EXPLODING ステートマシン）
        if (!isTimeStopped) {
            for (let e of this.enemies) {
                if (e.type !== 'MINE' || !e.active) continue;
                // プレイヤーがトリガー範囲に入ったら起爆開始
                if (e.state === 'IDLE' && e.playerInRange(this.player.x, this.player.y)) {
                    e.trigger();
                }
                // 爆発処理
                if (e.state === 'EXPLODING') {
                    e.active = false;
                    this.createExplosion(e.x, e.y, e.color);
                    this.createExplosion(e.x, e.y, '#FFAA00');
                    if (this.audio) this.audio.playMineExplode();
                    if (this.damageCooldown <= 0 && e.playerInBlastRange(this.player.x, this.player.y)) {
                        this.resolveCollision('MINE', 'BODY', e);
                        if (this.state !== 'PLAYING') return;
                    }
                }
            }
        }

        // ビーム系のヒット判定 (BeamBoss / OctaBeam / SniperBeam)
        // ビーム免疫: ビーム系同士・スパークはビームで死なない
        const BEAM_IMMUNE = new Set(['BEAM_BOSS', 'OCTA_BEAM', 'SNIPER_BEAM', 'SPARKS', 'MINE', 'GHOST']);
        if (!isTimeStopped) {
            const beamEnemyTypes = ['BEAM_BOSS', 'OCTA_BEAM', 'SNIPER_BEAM'];
            for (let e of this.enemies) {
                if (!beamEnemyTypes.includes(e.type) || !e.hasActiveBeam()) continue;

                // ビームが他の敵を倒す（免疫持ちは除外）
                let beamKilledAny = false;
                for (let other of this.enemies) {
                    if (other === e || !other.active) continue;
                    if (BEAM_IMMUNE.has(other.type)) continue;
                    if (e.enemyHitByBeam(other)) {
                        other.active = false;
                        this.createExplosion(other.x, other.y, other.color || '#FF4400');
                        beamKilledAny = true;
                        if (this.achievements) this.achievements.onFriendlyFire();
                    }
                }
                // 全敵撃破クリア判定
                if (beamKilledAny) {
                    this.checkClear();
                    if (this.state !== 'PLAYING') return;
                }

                // プレイヤーヒット
                if (this.damageCooldown <= 0 && e.playerHitByBeam(this.player.x, this.player.y)) {
                    this.resolveCollision(e.type, 'BODY', e);
                    if (this.state !== 'PLAYING') return;
                    break;
                }
            }
        }

        // Snake侵食による背景再描画 (2フレームに1回スロットル)
        if (this.needsBgUpdate && this.frameCount % 2 === 0) {
            this.renderer.updateBackground(
                this.grid,
                { hidden: this.assets.get('hidden'), reveal: this.assets.get('reveal') },
                this.overlayAlpha
            );
            this.needsBgUpdate = false;
        }

        this.updateUI(currentRisk);
    }

    movePlayer() {
        const isCurrentlyOnWall = (this.grid.get(this.player.x, this.player.y) === CONFIG.STATE.WALL);
        if (isCurrentlyOnWall) this.safePos = { x: this.player.x, y: this.player.y };

        let dxInput = this.input.x;
        let dyInput = this.input.y;
        if (this.keyState.ArrowRight) dxInput = 1;
        if (this.keyState.ArrowLeft)  dxInput = -1;
        if (this.keyState.ArrowDown)  dyInput = 1;
        if (this.keyState.ArrowUp)    dyInput = -1;
        const mergedInput = { x: dxInput, y: dyInput };

        let dx = 0, dy = 0;
        if (isCurrentlyOnWall) {
            this.isBoosting = false;

            if (mergedInput.x === 0 && mergedInput.y === 0) { this.currentDir = { x: 0, y: 0 }; return false; }
            dx = mergedInput.x; dy = mergedInput.y; this.currentDir = { x: dx, y: dy };
        } else {
            if (this.isConcentrating) {
                if (mergedInput.x === 0 && mergedInput.y === 0) {
                    return false;
                }
                if (mergedInput.x !== 0 || mergedInput.y !== 0) {
                    if (!(this.currentDir.x === -mergedInput.x && this.currentDir.y === -mergedInput.y)) {
                        this.currentDir = { x: mergedInput.x, y: mergedInput.y };
                    }
                }
                this.turnQueue = null;
            } else {
                if (mergedInput.x !== 0 || mergedInput.y !== 0) {
                    if (!(this.currentDir.x === -mergedInput.x && this.currentDir.y === -mergedInput.y)) {
                        if (!this.turnQueue) {
                            if (this.currentDir.x !== mergedInput.x || this.currentDir.y !== mergedInput.y) {
                                this.turnQueue = { ...mergedInput, delay: this.options.turnDelay };
                            }
                        } else {
                            if (this.turnQueue.x !== mergedInput.x || this.turnQueue.y !== mergedInput.y) {
                                this.turnQueue = { ...mergedInput, delay: this.options.turnDelay };
                            }
                        }
                    }
                }
                if (this.turnQueue) {
                    this.turnQueue.delay--;
                    if (this.turnQueue.delay <= 0) {
                        this.currentDir = { x: this.turnQueue.x, y: this.turnQueue.y };
                        this.turnQueue = null;
                    }
                }
            }
            dx = this.currentDir.x; dy = this.currentDir.y;
        }

        if (dx !== 0 || dy !== 0) this.lastDir = { x: dx, y: dy };
        if (dx === 0 && dy === 0) return false;

        let nx = this.player.x + dx; let ny = this.player.y + dy;
        if (!this.grid.isValid(nx, ny)) {
            if (!isCurrentlyOnWall) this.currentDir = { x: 0, y: 0 };
            return false;
        }
        const nextIsEmpty = this.grid.get(nx, ny) === CONFIG.STATE.EMPTY;
        if (isCurrentlyOnWall && nextIsEmpty) {
            this.voidEntryTimer++;
            if (this.voidEntryTimer < this.VOID_ENTRY_THRESHOLD) return false;
        } else { this.voidEntryTimer = 0; }

        let stepCost = 1;
        if (dx !== 0 && dy !== 0) stepCost = this.DIAGONAL_COST_FACTOR;

        if (dx !== 0 && dy !== 0 && this.grid.get(nx, ny) === CONFIG.STATE.EMPTY) {
            const fillX = this.player.x + dx;
            const fillY = this.player.y;
            if (this.grid.isValid(fillX, fillY) && this.grid.get(fillX, fillY) === CONFIG.STATE.EMPTY) {
                    this.grid.set(fillX, fillY, CONFIG.STATE.TRAIL);
                    this.trailPoints.push({ x: fillX, y: fillY, cost: 1 });
            }
        }

        if (this.grid.get(nx, ny) === CONFIG.STATE.TRAIL) {
            this.onHit(); return false;
        }

        this.player.x = nx; this.player.y = ny;
        const landOnWall = this.grid.get(nx, ny) === CONFIG.STATE.WALL;

        if (landOnWall) {
            this.isBoosting = false;

            if (this.trailPoints.length > 0) {
                const result = this.grid.processSplit(this.trailPoints, this.enemies);
                
                if (result.claimedCount === 0) {
                    console.log("Fill logic triggered Revert. Player safe.");
                } else {
                    this.claimedCount += result.claimedCount;
                    if (this.audio) this.audio.play('se_claim', 1.0);

                    if (result.filledIndices && result.filledIndices.length > 0) {
                        this.claimEffects.push(new ClaimEffect(result.filledIndices, this.grid.cols));
                        
                        const centerIdx = result.filledIndices[Math.floor(result.filledIndices.length / 2)];
                        const cx = centerIdx % this.grid.cols;
                        const cy = Math.floor(centerIdx / this.grid.cols);
                        this.createExplosion(cx, cy, '#00FFFF');
                    }

                    this.renderer.updateBackground(this.grid, { hidden: this.assets.get('hidden'), reveal: this.assets.get('reveal') }, this.overlayAlpha);

                    // 実績フック: 囲み完了
                    if (this.achievements) {
                        this.achievements.onTrap(result.trappedEnemies, result.claimedCount, this.totalCount);
                        this.achievements.onTrailCells(this.trailPoints.length);
                    }

                    if (result.trappedEnemies.length > 0) {
                        result.trappedEnemies.forEach(e => {
                            this.applyEnemyBonus(e);
                            e.active = false;
                            this.createExplosion(e.x, e.y, e.color);
                        });
                    }
                    this.checkClear();
                }
                this.trailPoints = [];
                this.stopTrailSound();
                this.voidEntryTimer = 0;
                this.currentDir = { x: 0, y: 0 };
                this.turnQueue = null;
                this.updateUI(0);
                return true;
            }
        } else {
            this.updateTrailSound(true);
            this.grid.set(nx, ny, CONFIG.STATE.TRAIL);
            this.trailPoints.push({ x: nx, y: ny, cost: stepCost });
        }
        return false;
    }

    applyEnemyBonus(enemy) {
        let msg = "";
        let color = "#FFFFFF";
        if (enemy.type === 'QIX' || enemy.type === 'SHOOTER' || enemy.type === 'TRACKER') {
            const speed = enemy.currentSpeed || enemy.speed || 4.0;
            const bonusAmount = Math.floor(speed * 5);
            this.mp = Math.min(this.mp + bonusAmount, CONFIG.MAX_MP);
            msg = `MP +${bonusAmount}`;
            color = '#00FF00';
        } else if (enemy.type === 'BEAM_BOSS') {
            const bonusAmount = 35;
            this.mp = Math.min(this.mp + bonusAmount, CONFIG.MAX_MP);
            msg = `BOSS DOWN! MP +${bonusAmount}`;
            color = '#FF8800';
        } else if (enemy.type === 'OCTA_BEAM') {
            const bonusAmount = 40;
            this.mp = Math.min(this.mp + bonusAmount, CONFIG.MAX_MP);
            msg = `OCTA DOWN! MP +${bonusAmount}`;
            color = '#FF8800';
        } else if (enemy.type === 'SNIPER_BEAM') {
            const bonusAmount = 30;
            this.mp = Math.min(this.mp + bonusAmount, CONFIG.MAX_MP);
            msg = `SNIPER DOWN! MP +${bonusAmount}`;
            color = '#FF44AA';
        } else if (enemy.type === 'SNAKE') {
            const bonusAmount = 25;
            this.mp = Math.min(this.mp + bonusAmount, CONFIG.MAX_MP);
            this.enemySlowTimer = Math.max(this.enemySlowTimer, 150);
            this.enemySlowMult = 0;
            msg = `SNAKE DOWN! FREEZE! MP +${bonusAmount}`;
            color = '#00FF44';
        } else if (enemy.type === 'GHOST') {
            this.enemySlowTimer = Math.max(this.enemySlowTimer, 180);
            this.enemySlowMult = Math.min(this.enemySlowMult, 0.35);
            msg = `GHOST DOWN! SLOW!`;
            color = '#8888FF';
        } else if (enemy.type === 'MINE') {
            const bonusAmount = 15;
            this.mp = Math.min(this.mp + bonusAmount, CONFIG.MAX_MP);
            msg = `MINE CLEARED! MP +${bonusAmount}`;
            color = '#FF8800';
        } else if (enemy.type === 'SPARKS') {
            const duration = Math.floor(enemy.speed * 40);
            this.timeStopTimer = duration;
            msg = "TIME STOP!";
            color = '#AAAAFF';
        }
        this.showMessage(msg, 2000, color, 1);
    }

    getPlayerStatus() {
        if (this.trailPoints.length > 0) return 'DANGER';
        const cx = this.player.x;
        const cy = this.player.y;
        const neighbors = [{x:0, y:-1}, {x:0, y:1}, {x:-1, y:0}, {x:1, y:0}];
        for(let n of neighbors) {
            const nx = Math.floor(cx + n.x);
            const ny = Math.floor(cy + n.y);
            if (this.grid.isValid(nx, ny)) {
                if (this.grid.get(nx, ny) === CONFIG.STATE.EMPTY) {
                    return 'BORDER';
                }
            }
        }
        return 'SAFE';
    }

    resolveCollision(sourceType, hitLocation, entity) {
        const playerStatus = this.getPlayerStatus();

        if (hitLocation === 'TRAIL') {
            if (playerStatus !== 'DANGER') {
                // player has no active trail; entity touching a stale cell — ignore
                return;
            }
            if (this.damageCooldown > 0) return;
            if (sourceType === 'SPARKS') {
                entity.active = false;
                this.createExplosion(entity.x, entity.y, entity.color);
                if (this.achievements) this.achievements.onSparksHit();
                if (this.audio) this.audio.playSparks();
            }
            if (sourceType === 'GHOST') return; // 幽霊はトレイルをすり抜ける
            this.createExplosion(this.player.x, this.player.y, '#FF00FF');
            this.onHit();
            if (this.state === 'PLAYING') this.checkClear();
            return;
        }

        if (hitLocation === 'BODY') {
            if (playerStatus === 'DANGER') {
                if (this.damageCooldown > 0) return;
                if (sourceType === 'SPARKS') {
                    entity.active = false;
                    this.createExplosion(entity.x, entity.y, entity.color);
                    if (this.achievements) this.achievements.onSparksHit();
                    if (this.audio) this.audio.playSparks();
                }
                if (sourceType === 'GHOST') {
                    entity.active = false;
                    this.createExplosion(entity.x, entity.y, entity.color);
                }
                this.onHit();
                if (this.state === 'PLAYING') this.checkClear();
                return;
            }

            if (playerStatus === 'BORDER') {
                if (this.damageCooldown > 0) return;

                if (sourceType === 'SPARKS') {
                    entity.active = false;
                    this.createExplosion(entity.x, entity.y, entity.color);
                    if (this.achievements) this.achievements.onSparksHit();
                    this.onHit(50);
                    if (this.state === 'PLAYING') this.checkClear();
                    return;
                }
                if (sourceType === 'PROJECTILE') {
                    entity.active = false;
                    this.createExplosion(entity.x, entity.y, entity.color);
                    this.onHit(30);
                    return;
                }
                if (sourceType === 'BEAM_BOSS' || sourceType === 'OCTA_BEAM') {
                    this.onHit(35);
                    return;
                }
                if (sourceType === 'SNIPER_BEAM') {
                    this.onHit(40);
                    return;
                }
                if (sourceType === 'SNAKE') {
                    entity.active = false;
                    this.createExplosion(entity.x, entity.y, entity.color);
                    this.onHit(30);
                    return;
                }
                if (sourceType === 'MINE') {
                    if (this.achievements) this.achievements.onMineHit();
                    this.onHit(45);
                    return;
                }
                if (sourceType === 'GHOST') {
                    entity.active = false;
                    this.createExplosion(entity.x, entity.y, entity.color);
                    this.onHit(30);
                    return;
                }
                if (sourceType === 'QIX' || sourceType === 'SHOOTER' || sourceType === 'TRACKER') {
                    if (sourceType === 'TRACKER') {
                        entity.active = false;
                        this.createExplosion(entity.x, entity.y, '#FF3355');
                        this.onHit(40);
                        return;
                    }
                    this.onHit();
                    if (this.state === 'PLAYING') this.checkClear();
                    return;
                }
            }

            if (playerStatus === 'SAFE') {
                if (sourceType === 'PROJECTILE') {
                    entity.active = false;
                    this.createExplosion(entity.x, entity.y, entity.color);
                    return;
                }
                if (sourceType === 'TRACKER') {
                    entity.active = false;
                    this.createExplosion(entity.x, entity.y, '#FF3355');
                    this.onHit(40);
                    return;
                }
                if (sourceType === 'BEAM_BOSS' || sourceType === 'OCTA_BEAM') {
                    this.onHit(35);
                    return;
                }
                if (sourceType === 'SNIPER_BEAM') {
                    this.onHit(40);
                    return;
                }
                if (sourceType === 'SNAKE') {
                    entity.active = false;
                    this.createExplosion(entity.x, entity.y, entity.color);
                    this.onHit(30);
                    return;
                }
                if (sourceType === 'MINE') {
                    this.onHit(45);
                    return;
                }
                if (sourceType === 'GHOST') {
                    entity.active = false;
                    this.createExplosion(entity.x, entity.y, entity.color);
                    this.onHit(30);
                    return;
                }
                if (sourceType === 'SPARKS') {
                    entity.active = false;
                    this.createExplosion(entity.x, entity.y, entity.color);
                    if (this.achievements) this.achievements.onSparksHit();
                    this.onHit(50);
                    if (this.state === 'PLAYING') this.checkClear();
                    return;
                }
                // QIX/SHOOTER can't damage player in fully claimed territory
                return;
            }
        }
    }

    triggerBugEvent(x, y, reason) {
        console.warn(`[BUG DETECTED] ${reason}`);
        this.showMessage("?", 1000, '#FFFF00', 3);
        this.createExplosion(x, y, '#CCCCCC');
    }

    spawnSparks() {
        if (this.sparksSpawned) return;
        this.sparksSpawned = true;
        this.showMessage("WARNING!", 2000, '#FF0055', 2);
        
        const candidates = [];
        const cols = this.grid.cols;
        const rows = this.grid.rows;

        const isSpawnable = (x, y) => {
            if (this.grid.get(x, y) !== CONFIG.STATE.WALL) return false;
            for (let dy = -1; dy <= 1; dy++) {
                for (let dx = -1; dx <= 1; dx++) {
                    if (dx === 0 && dy === 0) continue;
                    const nx = x + dx;
                    const ny = y + dy;
                    if (this.grid.isValid(nx, ny) && this.grid.get(nx, ny) === CONFIG.STATE.EMPTY) {
                        return true;
                    }
                }
            }
            return false;
        };

        for(let y = 1; y < rows - 1; y++) {
            if (isSpawnable(1, y)) candidates.push({x: 1, y: y});
            if (isSpawnable(cols - 2, y)) candidates.push({x: cols - 2, y: y});
        }
        for(let x = 1; x < cols - 1; x++) {
            if (isSpawnable(x, 1)) candidates.push({x: x, y: 1});
            if (isSpawnable(x, rows - 2)) candidates.push({x: x, y: rows - 2});
        }

        if (candidates.length === 0) candidates.push({x: this.player.x, y: this.player.y});

        candidates.sort((a, b) => {
            const da = (a.x - this.player.x)**2 + (a.y - this.player.y)**2;
            const db = (b.x - this.player.x)**2 + (b.y - this.player.y)**2;
            return db - da; 
        });
        
        for(let i=0; i<this.sparksConfig.count; i++) {
            const pos = candidates[i % candidates.length];
            const s = new SparksEnemy({
                x: pos.x, y: pos.y,
                speed: this.sparksConfig.speed,
                color: this.sparksConfig.color,
                type: 'SPARKS'
            }, this.player);
            this.enemies.push(s);
        }
    }

    createExplosion(x, y, color) {
        for (let i = 0; i < 30; i++) {
            this.particles.push(new Particle(x, y, color));
        }
    }

    createGatheringParticles(targetX, targetY, color = '#00FFFF') {
        for (let i = 0; i < 3; i++) {
            const angle = Math.random() * Math.PI * 2;
            const dist = 50 + Math.random() * 50; 
            const x = targetX + Math.cos(angle) * dist;
            const y = targetY + Math.sin(angle) * dist;
            const vx = (targetX - x) / 15;
            const vy = (targetY - y) / 15;
            const p = new Particle(x, y, color);
            p.vx = vx;
            p.vy = vy;
            p.life = 1.2;
            p.decay = 0.05;
            this.particles.push(p);
        }
    }

    activateBoost() {
        if (this.isConcentrating) return;
        const isCurrentlyOnWall = (this.grid.get(this.player.x, this.player.y) === CONFIG.STATE.WALL);
        if (!isCurrentlyOnWall && this.boostStock > 0 && this.mp >= CONFIG.BOOST_COST) {
            this.boostStock--;
            this.mp -= CONFIG.BOOST_COST;
            this.isBoosting = true;
            if (this.achievements) this.achievements.onBoostUsed();
            if (this.audio) this.audio.playBoost();
            this.updateUI();
        }
    }

    updateTrailSound(isDrawing) {
        if (isDrawing) {
            if (!this.trailSoundSource)
                this.trailSoundSource = this.audio.play('se_trail', 0.3, true);
        } else {
            this.stopTrailSound();
        }
    }

    stopTrailSound() {
        if (this.trailSoundSource) {
            try { this.trailSoundSource.stop(); } catch(e) {}
            this.trailSoundSource = null;
        }
    }

    onHit(fixedCost = null) {
        // 実績フック: ダメージ & 軌跡消費
        if (this.achievements) {
            this.achievements.onDamage();
            if (this.trailPoints.length > 0) this.achievements.onTrailCells(this.trailPoints.length);
        }
        const cost = (fixedCost !== null) ? fixedCost : this.calculateRisk();
        this.shakeTimer = 20;
        this.stopTrailSound();
        if (this.audio) { this.audio.pauseBGM(); this.audio.playDeath(); }
        this.startExplosion(cost);
    }

    startExplosion(cost) {
        this.state = 'EXPLODING';
        this.explosionTimer = 20; 
        this.pendingCost = cost;
        this.isBoosting = false;
        
        this.player.visible = false;
        this.deadPos.x = this.player.x;
        this.deadPos.y = this.player.y;
        
        this.enemies = this.enemies.filter(e => e.active);

        this.createExplosion(this.player.x, this.player.y, '#FF0000');
        this.createExplosion(this.player.x, this.player.y, '#FFFFFF');
    }

    startFreeze() {
        this.state = 'FREEZE';
        this.freezeTimer = 30;
        if (this.audio) this.audio.play('se_damage', 0.8);

        this.mp = Math.max(0, this.mp - this.pendingCost);
        this.updateUI(null, true);

        document.body.classList.add('rewind-mode'); 
    }

    updateRewind() {
        if (this.trailPoints.length === 0) {
            if (this.rewindWaitTimer > 0) {
                this.rewindWaitTimer--;
                const t = 1.0 - (this.rewindWaitTimer / 40.0);
                this.player.x = this.deadPos.x + (this.safePos.x - this.deadPos.x) * t;
                this.player.y = this.deadPos.y + (this.safePos.y - this.deadPos.y) * t;
                return;
            }
            this.startFreezeEnd(); 
            return; 
        }
        
        const rewindSpeed = 5;
        for (let i = 0; i < rewindSpeed; i++) {
            if (this.trailPoints.length === 0) break;
            const p = this.trailPoints.pop();
            this.grid.set(p.x, p.y, CONFIG.STATE.EMPTY);
            this.player.x = p.x;
            this.player.y = p.y;
        }
    }

    startFreezeEnd() {
        this.state = 'FREEZE_END'; 
        this.freezeTimer = 30; 
        this.currentDir = { x: 0, y: 0 }; 
        this.turnQueue = null;
        this.player.x = this.safePos.x; 
        this.player.y = this.safePos.y;
        this.player.visible = true; 
        document.body.classList.remove('rewind-mode'); 
        document.body.classList.add('color-recover');
    }

    checkClear() {
        const data = this.currentStageList[this.stageIndex];
        const quota = data ? data.clearQuota : 0.75;
        const percent = this.claimedCount / this.totalCount;
        const allEnemiesDead = this.enemies.every(e => !e.active);
        
        if (percent >= quota || allEnemiesDead) { 
            this.stageClear(); 
        }
    }

    stageClear() {
        this.stopTrailSound();
        if (this.audio) this.audio.stopBGM();
        // 実績フック: クリア
        if (this.achievements) {
            const finalPct = this.claimedCount / this.totalCount;
            const allWiped = this.enemies.every(e => !e.active);
            this.achievements.onClear(finalPct, this.mp, STAGE_DATA.FREE, allWiped);
        }

        this.enemies = [];
        this.projectiles = [];
        this.currentDir = { x: 0, y: 0 };
        this.timeStopTimer = 0;
        this.enemySlowTimer = 0;
        this.enemySlowMult = 1.0;
        this.msgPriority = 0;
        this.isConcentrating = false;

        document.body.classList.remove('time-stop-mode');
        document.body.classList.remove('danger-mode');
        document.body.classList.remove('concentrate-mode');
        document.getElementById('ui').style.opacity = '0';
        document.getElementById('controls').style.opacity = '0';
        document.getElementById('reset-btn').style.display = 'none';

        this.trailPoints = [];

        // 塗り広がりアニメが残っていればそれが終わってからSEEPへ
        if (this.claimEffects.length > 0) {
            this.state = 'CLEAR_ANIM';
        } else {
            this.state = 'CLEAR_SEEP';
            this.seepAlpha = 0;
        }
    }

    startClearWait() {
        this.state = 'CLEAR_WAIT';
        this.clearWaitTimer = 300; 
        this.grid.data.fill(CONFIG.STATE.WALL);
        this.showMessage("OVERWRITE<br>SUCCESS", 5000, '#BC13FE', 3);
    }

    nextStage() {
        if (this._isCustomStage) {
            this._isCustomStage = false;
            this.state = 'TITLE';
            this.creator.returnFromPlay();
            return;
        }
        this.returnToMenu();
    }

    loadCustomStage(stageData) {
        this._isCustomStage = true;
        this.currentStageList = [stageData];
        const _csn = stageData._stageName || stageData.name || '';
        document.getElementById('stage-info').innerText = _csn ? `「${_csn}」` : 'CUSTOM';
        this.loadStage(0);
    }

    gameOver() {
        this.stopTrailSound();
        if (this.achievements) this.achievements.onGameOver();
        if (this.audio) { this.audio.stopBGM(); this.audio.playGameOver(); }
        if (this._isCustomStage) {
            this._isCustomStage = false;
            this.state = 'TITLE';
            this.creator.returnFromPlay();
            return;
        }
        this.state = 'GAMEOVER';
        this.player.visible = false;
        setTimeout(() => { this.ui.showGameOver(); }, 1000);
    }

    render() {
        let offsetX = 0;
        let offsetY = 0;
        if (this.shakeTimer > 0) {
            offsetX = (Math.random() - 0.5) * 10;
            offsetY = (Math.random() - 0.5) * 10;
        }

        const renderParams = {
            gridSystem: this.grid,
            assets: { hidden: this.assets.get('hidden'), reveal: this.assets.get('reveal') },
            player: this.player,
            enemies: this.enemies,
            projectiles: this.projectiles,
            trailPoints: this.trailPoints,
            direction: this.lastDir,
            isGlitchActive: this.state === 'REWIND',
            particles: this.particles,
            claimEffects: this.claimEffects, 
            items: [], 
            state: this.state,
            introParams: {
                viewTimer: this.introViewTimer,
                ripples: this.erosionRipples,
                alpha: this.currentOverlayAlpha, // ★変更: 常に固定のターゲット値を渡す
                seepAlpha: this.seepAlpha 
            }, 
            shakeOffset: { x: offsetX, y: offsetY }
        };

        this.renderer.render(renderParams);
    }

    loop() {
        requestAnimationFrame(() => this.loop());
        this.update();
        this.render();
    }
    
    bindInput() {
        // ダイアログタップで次へ
        document.getElementById('dialogue-overlay').addEventListener('click', () => {
            if (this.state === 'DIALOGUE') this.advanceDialogue();
        });

        // 中断確認ボタン
        document.getElementById('pause-ok').onclick = () => this._confirmAbort();
        document.getElementById('pause-cancel').onclick = () => this._cancelAbort();

        document.addEventListener('keydown', e => {
            // ESC: 各画面で「戻る」
            if (e.key === 'Escape') {
                e.preventDefault();
                this._handleEscape();
                return;
            }

            // タイトル画面で E キー → エディタを開く
            if (this.state === 'TITLE' && (e.key === 'e' || e.key === 'E')) {
                openEditor();
                return;
            }

            if (this.state === 'TITLE') {
                if (e.key === 'ArrowUp') {
                    this.menuIndex = (this.menuIndex - 1 + this.menuItems.length) % this.menuItems.length;
                    this.updateMenuVisual();
                }
                else if (e.key === 'ArrowDown') {
                    this.menuIndex = (this.menuIndex + 1) % this.menuItems.length;
                    this.updateMenuVisual();
                }
                else if (e.key === 'Enter' || e.key === 'z' || e.key === 'Z' || e.key === ' ') {
                    this.executeMenuAction();
                }
                return;
            }

            if (this.state === 'DIALOGUE') {
                if (e.key === 'Enter' || e.key === 'z' || e.key === 'Z' || e.key === ' ') {
                    this.advanceDialogue();
                }
                return;
            }

            // 中断確認オーバーレイ表示中
            if (document.getElementById('pause-overlay').style.display !== 'none') {
                if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
                    this._pauseFocusIndex = this._pauseFocusIndex === 0 ? 1 : 0;
                    this._updatePauseFocus();
                } else if (e.key === 'Enter' || e.key === 'z' || e.key === 'Z') {
                    if (this._pauseFocusIndex === 0) this._confirmAbort();
                    else this._cancelAbort();
                }
                e.preventDefault();
                return;
            }

            if (this.state === 'GAMEOVER') {
                this.ui.gameOverKey(e.key);
                e.preventDefault();
                return;
            }

            if (this.state === 'MENU') {
                this.ui.stageSelectKey(e.key);
                if (['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','Enter'].includes(e.key)) e.preventDefault();
                return;
            }
            
            switch (e.key) {
                case 'ArrowUp': this.keyState.ArrowUp = true; break;
                case 'ArrowDown': this.keyState.ArrowDown = true; break;
                case 'ArrowLeft': this.keyState.ArrowLeft = true; break;
                case 'ArrowRight': this.keyState.ArrowRight = true; break;
                
                case 'Shift':
                    if (!this.isConcentrating && this.mp > 0) {
                        this.isConcentrating = true;
                        this.isBoosting = false;
                        document.body.classList.add('concentrate-mode');
                        if (this.audio) this.audio.playSlowOn();
                    }
                    break;
                case 'z': case 'Z': case ' ': 
                    if (!this.isConcentrating) this.activateBoost(); 
                    break;
            }
        });
        document.addEventListener('keyup', (e) => {
            switch (e.key) {
                case 'ArrowUp': this.keyState.ArrowUp = false; break;
                case 'ArrowDown': this.keyState.ArrowDown = false; break;
                case 'ArrowLeft': this.keyState.ArrowLeft = false; break;
                case 'ArrowRight': this.keyState.ArrowRight = false; break;
            }
            if (e.key === 'Shift') {
                if (this.isConcentrating && this.audio) this.audio.playSlowOff();
                this.isConcentrating = false;
                document.body.classList.remove('concentrate-mode');
            }
        });
        
        // ===== バーチャルジョイスティック =====
        const pad  = document.getElementById('joystick-area');
        const dot  = document.getElementById('joystick-dot');
        const joyU = document.getElementById('joy-u');
        const joyD = document.getElementById('joy-d');
        const joyL = document.getElementById('joy-l');
        const joyR = document.getElementById('joy-r');
        const JOY_R    = 55;   // ドットが動ける最大半径(px)
        const getDeadPx = () => this.options.stickDead;

        const snapDir = (dx, dy) => {
            const DEAD_PX = getDeadPx();
            const d = Math.sqrt(dx * dx + dy * dy);
            if (d < DEAD_PX) return { x: 0, y: 0 };
            const DIAG_THRESH = 0.35; // sin(~20deg) — 斜めゾーン
            const ratio = d > 0 ? Math.min(Math.abs(dx), Math.abs(dy)) / d : 0;
            const nx = Math.abs(dx) > DEAD_PX * 0.5 ? (dx > 0 ? 1 : -1) : 0;
            const ny = Math.abs(dy) > DEAD_PX * 0.5 ? (dy > 0 ? 1 : -1) : 0;
            if (ratio >= DIAG_THRESH) return { x: nx, y: ny }; // 斜め
            return Math.abs(dx) >= Math.abs(dy)
                ? { x: nx, y: 0 }
                : { x: 0, y: ny };
        };

        const updateDot = (dx, dy) => {
            const d = Math.sqrt(dx * dx + dy * dy);
            const r = Math.min(d, JOY_R);
            const ratio = d > 0 ? r / d : 0;
            const ox = dx * ratio;
            const oy = dy * ratio;
            // パーセント変換 (pad 140px → 半径70px)
            dot.style.left = (50 + ox / 70 * 50) + '%';
            dot.style.top  = (50 + oy / 70 * 50) + '%';
            // 矢印ハイライト
            const dir = snapDir(dx, dy);
            joyU.classList.toggle('active', dir.y < 0);
            joyD.classList.toggle('active', dir.y > 0);
            joyL.classList.toggle('active', dir.x < 0);
            joyR.classList.toggle('active', dir.x > 0);
        };

        const resetDot = () => {
            dot.style.left = '50%';
            dot.style.top  = '50%';
            [joyU, joyD, joyL, joyR].forEach(a => a.classList.remove('active'));
        };

        const padCenter = (r) => ({ cx: r.left + r.width / 2, cy: r.top + r.height / 2 });

        // Touch
        pad.addEventListener('touchstart', e => {
            e.preventDefault();
            const r = pad.getBoundingClientRect();
            const { cx, cy } = padCenter(r);
            const t = e.touches[0];
            const dx = t.clientX - cx, dy = t.clientY - cy;
            this.input = snapDir(dx, dy);
            updateDot(dx, dy);
        }, { passive: false });

        pad.addEventListener('touchmove', e => {
            e.preventDefault();
            const r = pad.getBoundingClientRect();
            const { cx, cy } = padCenter(r);
            const t = e.touches[0];
            const dx = t.clientX - cx, dy = t.clientY - cy;
            this.input = snapDir(dx, dy);
            updateDot(dx, dy);
        }, { passive: false });

        pad.addEventListener('touchend',   () => { this.input = { x:0, y:0 }; resetDot(); });
        pad.addEventListener('touchcancel',() => { this.input = { x:0, y:0 }; resetDot(); });

        // Mouse (PC デバッグ用)
        let joyMouseDown = false;
        pad.addEventListener('mousedown', e => {
            joyMouseDown = true;
            const r = pad.getBoundingClientRect();
            const { cx, cy } = padCenter(r);
            const dx = e.clientX - cx, dy = e.clientY - cy;
            this.input = snapDir(dx, dy);
            updateDot(dx, dy);
        });
        document.addEventListener('mousemove', e => {
            if (!joyMouseDown) return;
            const r = pad.getBoundingClientRect();
            const { cx, cy } = padCenter(r);
            const dx = e.clientX - cx, dy = e.clientY - cy;
            this.input = snapDir(dx, dy);
            updateDot(dx, dy);
        });
        document.addEventListener('mouseup', () => {
            if (!joyMouseDown) return;
            joyMouseDown = false;
            this.input = { x:0, y:0 };
            resetDot();
        });

        // ===== SLOW ボタン =====
        const btnSlow = document.getElementById('btn-slow');
        const slowOn = (e) => {
            e.preventDefault();
            if (this.mp > 0 && !this.isConcentrating) {
                this.isConcentrating = true;
                this.isBoosting = false;
                document.body.classList.add('concentrate-mode');
                if (this.audio) this.audio.playSlowOn();
            }
            btnSlow.classList.add('active');
        };
        const slowOff = (e) => {
            e.preventDefault();
            if (this.isConcentrating && this.audio) this.audio.playSlowOff();
            this.isConcentrating = false;
            document.body.classList.remove('concentrate-mode');
            btnSlow.classList.remove('active');
        };
        btnSlow.addEventListener('touchstart', slowOn,  { passive: false });
        btnSlow.addEventListener('touchend',   slowOff);
        btnSlow.addEventListener('touchcancel',slowOff);
        btnSlow.addEventListener('mousedown',  slowOn);
        btnSlow.addEventListener('mouseup',    slowOff);
        btnSlow.addEventListener('mouseleave', slowOff);

        // ===== BOOST ボタン =====
        const btnBoost = document.getElementById('btn-boost');
        const triggerBoost = (e) => {
            e.preventDefault();
            this.activateBoost();
            btnBoost.classList.add('active');
            setTimeout(() => btnBoost.classList.remove('active'), 200);
        };
        btnBoost.addEventListener('touchstart', triggerBoost, { passive: false });
        btnBoost.addEventListener('mousedown',  triggerBoost);
    }
}
const game = new Game();
game.init();