{"asset_map": {"soul_heart": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/soul_heart_2cf896e9-485b-44df-9710-a42de32cc896.webp", "type": "image", "prompt": "Generate a 32x32 pixel art sprite of a bright red heart. Solid red (#FF0000) with a subtle darker red outline. Simple, iconic Undertale style, symmetrical, clean pixel edges, transparent background.", "description": "The red heart soul sprite representing the player's target in the battle box.", "subtype": "character", "aspect_ratio": [732, 664], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "bone_small_vertical": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/bone_small_vertical_da3e2a12-484a-4646-a6da-bcf4650c64e0.webp", "type": "image", "prompt": "Generate a 16x64 pixel art sprite of a vertical white bone. Rounded ends, slightly narrower middle. Pure white (#FFFFFF) with subtle light gray shading (#F0F0F0). Minimalist Undertale aesthetic, clean pixel edges, transparent background.", "description": "A small vertical white bone projectile for horizontal movement attacks.", "subtype": "object", "aspect_ratio": [428, 1222], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "bone_small_horizontal": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/bone_small_horizontal_de24cb32-958c-45a9-af1c-62e86f2f32da.webp", "type": "image", "prompt": "Generate a 64x16 pixel art sprite of a horizontal white bone. Rounded ends, narrower middle. Pure white (#FFFFFF) with subtle light gray shading (#F0F0F0). Minimalist Undertale aesthetic, side view, clean pixel edges, transparent background.", "description": "A small horizontal white bone projectile for vertical movement attacks.", "subtype": "object", "aspect_ratio": [1150, 405], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "bone_large_vertical": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/bone_large_vertical_f02846db-ccee-4715-bb67-33c127696502.webp", "type": "image", "prompt": "Generate a 32x128 pixel art sprite of a large vertical white bone. Same style and proportions as a small bone but larger. Pure white (#FFFFFF) with subtle light gray shading (#F0F0F0). Undertale aesthetic, clean pixel edges, transparent background.", "description": "A large imposing vertical white bone for major obstacles.", "subtype": "object", "aspect_ratio": [456, 1499], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "blue_bone_vertical": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/blue_bone_vertical_733cd18b-53d9-49e8-942f-87b282ccac7f.webp", "type": "image", "prompt": "Generate a 16x64 pixel art sprite of a vertical cyan bone. Same shape as bone_small_vertical. Bright cyan color (#00FFFF) with darker cyan shading (#00CCCC). Clean pixel edges, transparent background, Undertale style.", "description": "A cyan colored vertical bone that represents a special hazard type.", "subtype": "object", "aspect_ratio": [438, 1139], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "gaster_blaster_head": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/gaster_blaster_head_1ed679cd-f7a3-4782-b7cd-c1251ab791b6.webp", "type": "image", "prompt": "Generate a 128x128 pixel art Gaster Blaster skull head, side view facing right. Iconic dragon-like skeletal head with an open jaw. Light gray color (#CCCCCC) with darker gray shading. Two glowing circular blue eyes (#0088FF). Clean pixel edges, dramatic Undertale style, transparent background.", "description": "The skull of a Gaster Blaster used for Sans' signature beam attacks.", "subtype": "object", "aspect_ratio": [858, 904], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "button_bone_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/button_bone_icon_06ec267c-1e3c-4367-92fd-b488ab412318.webp", "type": "image", "prompt": "Generate a 64x64 pixel art icon of a white bone. Simplified, bold design for UI use. White color (#FFFFFF), clean pixel edges, transparent background.", "description": "UI icon for the basic Bone Attack button.", "subtype": "ui", "aspect_ratio": [784, 759], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "button_blaster_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/button_blaster_icon_7346c71e-1abf-4897-929d-50af61c4d049.webp", "type": "image", "prompt": "Generate a 64x64 pixel art icon of a Gaster Blaster skull. Simplified design with glowing blue eyes. White/gray colors, clean pixel edges, transparent background.", "description": "UI icon for the Gaster Blaster attack button.", "subtype": "ui", "aspect_ratio": [729, 923], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "button_wall_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/button_wall_icon_7d830cfe-1783-4217-a23e-74239c165ca3.webp", "type": "image", "prompt": "Generate a 64x64 pixel art icon showing three vertical bones in a row. Suggests a 'wall' of bones. White color (#FFFFFF), clean pixel edges, transparent background.", "description": "UI icon representing the Bone Wall attack.", "subtype": "ui", "aspect_ratio": [648, 595], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "button_blue_bone_icon": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/button_blue_bone_icon_ad34265c-da50-418a-bcdc-edfedb3327d4.webp", "type": "image", "prompt": "Generate a 64x64 pixel art icon of a cyan bone. Same shape as button_bone_icon but cyan color (#00FFFF). Clean pixel edges, transparent background.", "description": "UI icon for the Blue Bone attack button.", "subtype": "ui", "aspect_ratio": [766, 747], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "background_texture": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/background_texture_1349f94f-4231-4cab-ad37-5722e5cdd6ad.webp", "type": "image", "prompt": "Generate a 256x256 pixel art texture that tiles seamlessly in all directions. Very dark gray/black void (#0A0A0A) with rare, tiny white pixel dots scattered randomly. Extremely subtle and minimalist, no visible seams, suitable for a looping game background.", "description": "A seamless dark void texture with subtle star-like dots for the background.", "subtype": "background", "aspect_ratio": [1024, 1024], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "bone_spawn_sound": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/bone_spawn_sound_af5234a6-e2c7-4c24-a005-091cd5dea4b7.mp3", "type": "audio", "prompt": "A sharp, short, and hollow percussive 'thunk' sound effect. Retro chiptune style, 8-bit aesthetic similar to Undertale. The sound should be quick, clean, and non-intrusive, resembling a wooden block or bone being struck. Optimized for frequent repetition during fast gameplay.", "description": "Short, hollow percussive click for spawning bone attacks, designed for rapid spamming.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.04, "loop": false, "animation": null}, "blaster_charge_sound": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/blaster_charge_sound_8962514c-ea40-4d5c-b9ad-e4a748dff554.mp3", "type": "audio", "prompt": "An ascending energy charging sound effect for a powerful weapon. Starts low and builds in pitch and intensity over two seconds. Features a mechanical hum and a glissando synth effect. Dark, ominous, and cinematic but in a lo-fi 8-bit retro style.", "description": "Rising energy charging sound for the Gaster Blaster with an ominous mechanical tone.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.88, "loop": false, "animation": null}, "blaster_fire_sound": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/blaster_fire_sound_d8a43bd5-511b-46e8-a90d-949415e22313.mp3", "type": "audio", "prompt": "A loud, powerful energy beam firing sound. Heavy 8-bit bass impact followed by a sustained high-energy laser hiss. Dramatic and punchy with a slight digital distortion. Retro chiptune aesthetic, impactful and satisfying to signal a major attack.", "description": "Powerful and bassy energy beam blast for the Gaster Blaster firing sequence.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 3.0, "loop": false, "animation": null}, "soul_dodge_sound": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/soul_dodge_sound_e711c038-5253-435b-990d-d73c05cde5ac.mp3", "type": "audio", "prompt": "A very short, high-pitched 'zip' or 'whoosh' sound effect. Minimalist retro chiptune style. Suggests high-speed agility and light movement. Very clean and subtle to ensure it remains pleasant even when triggered multiple times per second.", "description": "Extremely brief, high-pitched whoosh for the soul's evasion movement.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 0.6, "loop": false, "animation": null}, "sans_sprite": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/sans_sprite_93921aa9-5f56-499e-8d6a-953c6d16d6f5.webp", "type": "image", "prompt": "Generate a high-quality pixel art sprite of Sans from Undertale. He is a short, stout, smiling skeleton with white pupils, wearing an open blue hoodie with a light grey hood, black athletic shorts with white vertical stripes, and pink slippers. He should be in his iconic idle pose with hands tucked into his hoodie pockets, facing forward. Clean edges, minimalist shading, high-fidelity Undertale aesthetic, transparent background.", "description": "The main boss character sprite, a short smiling skeleton in a blue hoodie and pink slippers in pixel art style.", "subtype": "character", "aspect_ratio": [602, 992], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}}, "game_code": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Sans Simulator</title>\n    <style>\n        @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@700;900&family=Roboto:wght@500;700&display=swap');\n\n        :root {\n            --transition-fast: 150ms;\n            --transition-normal: 300ms;\n            --ease-out: cubic-bezier(0.0, 0, 0.2, 1);\n        }\n\n        body, html {\n            margin: 0;\n            padding: 0;\n            width: 100%;\n            height: 100%;\n            overflow: hidden;\n            touch-action: none;\n            -ms-touch-action: none;\n            font-family: 'Roboto', sans-serif;\n        }\n\n        #game-world {\n            position: relative;\n            width: 100%;\n            height: 100%;\n            background-color: #000000;\n        }\n\n        #game-canvas {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            image-rendering: pixelated;\n            image-rendering: crisp-edges;\n        }\n\n        .ui-overlay {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            pointer-events: none;\n        }\n\n        .ui-overlay > * {\n            pointer-events: auto;\n        }\n\n        .score-display {\n            position: absolute;\n            top: 24px;\n            left: 50%;\n            transform: translateX(-50%);\n            color: white;\n            text-align: center;\n            pointer-events: none;\n            display: flex;\n            gap: 32px;\n            font-family: 'Roboto', sans-serif;\n        }\n\n        .score-item {\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            gap: 4px;\n        }\n\n        .score-label {\n            font-size: 16px;\n            font-weight: 500;\n            color: rgba(255, 255, 255, 0.7);\n            text-transform: uppercase;\n            letter-spacing: 1px;\n            text-shadow: 0 2px 8px rgba(0,0,0,0.9);\n        }\n\n        .score-value {\n            font-family: 'Orbitron', monospace;\n            font-size: 32px;\n            font-weight: 900;\n            color: #00FFFF;\n            text-shadow: 0 0 20px rgba(0, 255, 255, 0.8),\n                         0 2px 8px rgba(0,0,0,0.9);\n        }\n\n        .button-container {\n            position: absolute;\n            bottom: 20px;\n            left: 50%;\n            transform: translateX(-50%);\n            display: grid;\n            grid-template-columns: repeat(5, 120px);\n            gap: 8px;\n            justify-content: center;\n            padding: 10px;\n            background: rgba(0, 0, 0, 0.5);\n            border-radius: 16px;\n            backdrop-filter: blur(4px);\n            border: 1px solid rgba(255, 255, 255, 0.1);\n        }\n\n        .tab-controls {\n            position: absolute;\n            bottom: 300px;\n            left: 50%;\n            transform: translateX(-50%);\n            display: flex;\n            gap: 12px;\n            pointer-events: auto;\n        }\n\n        .tab-btn {\n            padding: 12px 24px;\n            background: rgba(255, 255, 255, 0.1);\n            border: 2px solid rgba(255, 255, 255, 0.3);\n            border-radius: 30px;\n            color: white;\n            font-family: 'Orbitron', sans-serif;\n            font-size: 14px;\n            cursor: pointer;\n            transition: all 0.2s;\n        }\n\n        .tab-btn.active {\n            background: #00FFFF;\n            color: black;\n            border-color: #00FFFF;\n            box-shadow: 0 0 15px rgba(0, 255, 255, 0.5);\n        }\n\n        .button-group {\n            display: none;\n            grid-template-columns: repeat(5, 120px);\n            gap: 8px;\n        }\n\n        .button-group.active {\n            display: grid;\n        }\n\n        .attack-button {\n            width: 120px;\n            height: 120px;\n            background: rgba(255, 255, 255, 0.08);\n            border: 3px solid rgba(255, 255, 255, 0.4);\n            border-radius: 12px;\n            cursor: pointer;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n            gap: 8px;\n            transition: transform var(--transition-fast) var(--ease-out),\n                        background var(--transition-fast) var(--ease-out),\n                        border-color var(--transition-fast) var(--ease-out),\n                        box-shadow var(--transition-fast) var(--ease-out);\n            user-select: none;\n            -webkit-user-select: none;\n            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3),\n                        inset 0 1px 0 rgba(255, 255, 255, 0.1);\n        }\n\n        .attack-button:hover {\n            background: rgba(255, 255, 255, 0.15);\n            border-color: rgba(255, 255, 255, 0.6);\n            box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4),\n                        0 0 20px rgba(0, 255, 255, 0.3),\n                        inset 0 1px 0 rgba(255, 255, 255, 0.2);\n        }\n\n        .attack-button:active {\n            transform: scale(0.94);\n            background: rgba(255, 255, 255, 0.25);\n            border-color: #00FFFF;\n            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4),\n                        0 0 24px rgba(0, 255, 255, 0.6),\n                        inset 0 1px 0 rgba(255, 255, 255, 0.3);\n        }\n\n        .attack-button img {\n            width: 64px;\n            height: 64px;\n            image-rendering: pixelated;\n            pointer-events: none;\n            filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5));\n        }\n\n        .button-label {\n            font-family: 'Orbitron', monospace;\n            font-size: 14px;\n            font-weight: 700;\n            color: rgba(255, 255, 255, 0.9);\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n            text-shadow: 0 2px 4px rgba(0, 0, 0, 0.8);\n            pointer-events: none;\n        }\n\n        .reset-button {\n            position: absolute;\n            top: 24px;\n            right: 24px;\n            min-width: 96px;\n            min-height: 96px;\n            padding: 16px;\n            background: rgba(255, 0, 0, 0.2);\n            border: 3px solid rgba(255, 255, 255, 0.4);\n            border-radius: 12px;\n            color: white;\n            font-family: 'Orbitron', monospace;\n            font-size: 16px;\n            font-weight: 700;\n            cursor: pointer;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            transition: transform var(--transition-fast) var(--ease-out),\n                        background var(--transition-fast) var(--ease-out),\n                        border-color var(--transition-fast) var(--ease-out),\n                        box-shadow var(--transition-fast) var(--ease-out);\n            user-select: none;\n            -webkit-user-select: none;\n            text-transform: uppercase;\n            letter-spacing: 1px;\n            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3),\n                        inset 0 1px 0 rgba(255, 255, 255, 0.1);\n            text-shadow: 0 2px 4px rgba(0, 0, 0, 0.8);\n        }\n\n        .reset-button:hover {\n            background: rgba(255, 0, 0, 0.35);\n            border-color: rgba(255, 255, 255, 0.6);\n            box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4),\n                        0 0 20px rgba(255, 0, 0, 0.4),\n                        inset 0 1px 0 rgba(255, 255, 255, 0.2);\n        }\n\n        .reset-button:active {\n            transform: scale(0.94);\n            background: rgba(255, 0, 0, 0.5);\n            border-color: #FF0000;\n            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4),\n                        0 0 24px rgba(255, 0, 0, 0.6),\n                        inset 0 1px 0 rgba(255, 255, 255, 0.3);\n        }\n\n        .edit-selection {\n            position: absolute;\n            border: 2px dashed yellow;\n            pointer-events: none;\n            box-sizing: border-box;\n        }\n    </style>\n</head>\n<body>\n    <div id=\"game-world\">\n        <canvas id=\"game-canvas\"></canvas>\n        <div class=\"ui-overlay\">\n            <div class=\"score-display\">\n                <div class=\"score-item\">\n                    <div class=\"score-label\">Attacks</div>\n                    <div class=\"score-value\" id=\"attack-count\">0</div>\n                </div>\n                <div class=\"score-item\">\n                    <div class=\"score-label\">Near Misses</div>\n                    <div class=\"score-value\" id=\"near-miss-count\">0</div>\n                </div>\n                <div class=\"score-item\">\n                    <div class=\"score-label\">Damage</div>\n                    <div class=\"score-value\" id=\"damage-count\">0</div>\n                </div>\n            </div>\n            <div class=\"tab-controls\">\n                <div class=\"tab-btn active\" id=\"tab-attacks-btn\">Attacks</div>\n                <div class=\"tab-btn\" id=\"tab-modes-btn\">Modes</div>\n            </div>\n            <div class=\"button-container\">\n                <div id=\"group-attacks\" class=\"button-group active\">\n                    <div class=\"attack-button\" id=\"btn-bone\" data-attack=\"bone\">\n                        <img id=\"icon-bone\" src=\"\" alt=\"Bone\">\n                        <div class=\"button-label\">Bone</div>\n                    </div>\n                    <div class=\"attack-button\" id=\"btn-blaster\" data-attack=\"blaster\">\n                        <img id=\"icon-blaster\" src=\"\" alt=\"Blaster\">\n                        <div class=\"button-label\">Blaster</div>\n                    </div>\n                    <div class=\"attack-button\" id=\"btn-wall\" data-attack=\"wall\">\n                        <img id=\"icon-wall\" src=\"\" alt=\"Wall\">\n                        <div class=\"button-label\">Wall</div>\n                    </div>\n                    <div class=\"attack-button\" id=\"btn-blue\" data-attack=\"blue\">\n                        <img id=\"icon-blue\" src=\"\" alt=\"Blue\">\n                        <div class=\"button-label\">Blue Bone</div>\n                    </div>\n                    <div class=\"attack-button\" id=\"btn-circle\" data-attack=\"circle\">\n                        <img id=\"icon-circle\" src=\"\" alt=\"Circle\" style=\"filter: hue-rotate(90deg) saturate(2);\">\n                        <div class=\"button-label\">Circle</div>\n                    </div>\n                    <div class=\"attack-button\" id=\"btn-red\" data-attack=\"red\">\n                        <img id=\"icon-red\" src=\"\" alt=\"Red\" style=\"filter: hue-rotate(300deg) saturate(4);\">\n                        <div class=\"button-label\">Red Bone</div>\n                    </div>\n                    <div class=\"attack-button\" id=\"btn-slam\" data-attack=\"slam\">\n                        <img id=\"icon-slam\" src=\"\" alt=\"Slam\" style=\"filter: hue-rotate(180deg) brightness(1.5);\">\n                        <div class=\"button-label\">Slam</div>\n                    </div>\n                    <div class=\"attack-button\" id=\"btn-barrage\" data-attack=\"barrage\">\n                        <img id=\"icon-barrage\" src=\"\" alt=\"Barrage\" style=\"filter: hue-rotate(45deg) brightness(1.2);\">\n                        <div class=\"button-label\">Barrage</div>\n                    </div>\n                    <div class=\"attack-button\" id=\"btn-shooter\" data-attack=\"shooter\">\n                        <div id=\"icon-shooter\" style=\"width: 64px; height: 64px; background: #00FF00; border: 4px solid #8B0000; position: relative; box-sizing: border-box;\">\n                            <div style=\"position: absolute; top: 15px; left: 10px; width: 15px; height: 10px; background: red;\"></div>\n                            <div style=\"position: absolute; top: 15px; right: 10px; width: 15px; height: 10px; background: red;\"></div>\n                        </div>\n                        <div class=\"button-label\">Shooter</div>\n                    </div>\n                    <div class=\"attack-button\" id=\"btn-track\" data-attack=\"track\">\n                        <img id=\"icon-track\" src=\"\" alt=\"Track\" style=\"filter: hue-rotate(270deg) brightness(1.3);\">\n                        <div class=\"button-label\">Track</div>\n                    </div>\n                </div>\n\n                <div id=\"group-modes\" class=\"button-group\">\n                    <div class=\"attack-button\" id=\"btn-role\" data-attack=\"role\">\n                        <img id=\"icon-role\" src=\"\" alt=\"Role\">\n                        <div class=\"button-label\" id=\"label-role\">Sans</div>\n                    </div>\n                    <div class=\"attack-button\" id=\"btn-chase\" data-attack=\"chase\">\n                        <img id=\"icon-chase\" src=\"\" alt=\"Chase\" style=\"filter: brightness(1.5);\">\n                        <div class=\"button-label\">Chase</div>\n                    </div>\n                    <div class=\"attack-button\" id=\"btn-gravity\" data-attack=\"gravity\">\n                        <div style=\"width: 64px; height: 64px; display: flex; align-items: center; justify-content: center;\">\n                            <div style=\"width: 40px; height: 40px; border-radius: 50%; border: 4px solid #00FFFF; position: relative;\">\n                                <div style=\"position: absolute; bottom: 5px; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 10px solid transparent; border-right: 10px solid transparent; border-top: 15px solid #00FFFF;\"></div>\n                            </div>\n                        </div>\n                        <div class=\"button-label\" id=\"label-gravity\">Gravity OFF</div>\n                    </div>\n                    <div class=\"attack-button\" id=\"btn-shield\" data-attack=\"shield\">\n                        <div style=\"width: 64px; height: 64px; display: flex; align-items: center; justify-content: center; position: relative; animation: spin 4s linear infinite;\">\n                            <div style=\"width: 50px; height: 10px; background: #00FF00; position: absolute;\"></div>\n                            <div style=\"width: 10px; height: 50px; background: #00FF00; position: absolute;\"></div>\n                        </div>\n                        <style>@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }</style>\n                        <div class=\"button-label\" id=\"label-shield\">Shield OFF</div>\n                    </div>\n                </div>\n            </div>\n            <div class=\"reset-button\" id=\"reset-btn\">RESET</div>\n            <div id=\"attribution\" style=\"position: absolute; top: 10px; left: 10px; color: rgba(255, 255, 255, 0.4); font-size: 10px; font-family: 'Roboto', sans-serif; pointer-events: auto; text-align: left; line-height: 1.2;\">\n                Inspired by Sekai game called \"Sans Simulator\"<br>\n                Download Sekai somewhere\n            </div>\n        </div>\n    </div>\n\n    <script>\n        /**\n         * SANS SIMULATOR\n         * \n         * An inverted Undertale battle experience where you play as Sans with unlimited attacks,\n         * trying to overwhelm a hyper-intelligent, perfectly-dodging human soul.\n         * \n         * Core Mechanics:\n         * - Tap attack buttons to spawn bones and Gaster Blasters with no cooldowns\n         * - The red heart soul uses intelligent AI to dodge all attacks\n         * - Experiment with attack patterns and combinations\n         * - Score tracks attacks launched and near-misses\n         * - No traditional win condition - it's about the spectacle and experimentation\n         */\n\n        async function run(mode) {\n            const gameWorld = document.getElementById('game-world');\n            const canvas = document.getElementById('game-canvas');\n            const ctx = canvas.getContext('2d');\n\n            // Initialize display and resize handling\n            function resizeCanvas() {\n                canvas.width = gameWorld.clientWidth;\n                canvas.height = gameWorld.clientHeight;\n            }\n            resizeCanvas();\n            window.addEventListener('resize', resizeCanvas);\n\n            // Asset cache\n            const assets = {\n                images: {},\n                audio: {},\n                audioContext: null,\n                audioBuffers: {}\n            };\n\n            // Preload all assets\n            async function preloadAssets() {\n                const assetIds = [\n                    'soul_heart',\n                    'sans_sprite',\n                    'bone_small_vertical',\n                    'bone_small_horizontal',\n                    'bone_large_vertical',\n                    'blue_bone_vertical',\n                    'gaster_blaster_head',\n                    'button_bone_icon',\n                    'button_blaster_icon',\n                    'button_wall_icon',\n                    'button_blue_bone_icon',\n                    'background_texture',\n                    'bone_spawn_sound',\n                    'blaster_charge_sound',\n                    'blaster_fire_sound',\n                    'soul_dodge_sound'\n                ];\n\n                const imagePromises = [];\n                const audioPromises = [];\n\n                for (const id of assetIds) {\n                    const assetInfo = lib.getAsset(id);\n                    if (!assetInfo) continue;\n\n                    if (assetInfo.type === 'image') {\n                        const promise = new Promise((resolve, reject) => {\n                            const img = new Image();\n                            img.onload = () => {\n                                assets.images[id] = img;\n                                resolve();\n                            };\n                            img.onerror = reject;\n                            img.src = assetInfo.url;\n                        });\n                        imagePromises.push(promise);\n                    } else if (assetInfo.type === 'audio') {\n                        audioPromises.push(\n                            fetch(assetInfo.url)\n                                .then(response => response.arrayBuffer())\n                                .then(arrayBuffer => {\n                                    assets.audio[id] = arrayBuffer;\n                                })\n                        );\n                    }\n                }\n\n                await Promise.all([...imagePromises, ...audioPromises]);\n\n                // Set button icons\n                document.getElementById('icon-bone').src = assets.images.button_bone_icon.src;\n                document.getElementById('icon-blaster').src = assets.images.button_blaster_icon.src;\n                document.getElementById('icon-wall').src = assets.images.button_wall_icon.src;\n                document.getElementById('icon-blue').src = assets.images.button_blue_bone_icon.src;\n                document.getElementById('icon-circle').src = assets.images.button_blaster_icon.src;\n                document.getElementById('icon-red').src = assets.images.button_blue_bone_icon.src;\n                document.getElementById('icon-slam').src = assets.images.button_blue_bone_icon.src;\n                document.getElementById('icon-barrage').src = assets.images.button_blaster_icon.src;\n                document.getElementById('icon-chase').src = assets.images.sans_sprite.src;\n                document.getElementById('icon-track').src = assets.images.button_bone_icon.src;\n                document.getElementById('icon-role').src = assets.images.soul_heart.src;\n            }\n\n            await preloadAssets();\n\n            // Initialize Audio Context (on first user interaction)\n            function initAudio() {\n                if (!assets.audioContext) {\n                    assets.audioContext = new (window.AudioContext || window.webkitAudioContext)();\n                    \n                    // Decode all audio buffers\n                    for (const [id, arrayBuffer] of Object.entries(assets.audio)) {\n                        assets.audioContext.decodeAudioData(\n                            arrayBuffer.slice(0),\n                            (buffer) => {\n                                assets.audioBuffers[id] = buffer;\n                            },\n                            (error) => {\n                                console.error(`Error decoding audio ${id}:`, error);\n                            }\n                        );\n                    }\n                }\n            }\n\n            // Play sound effect\n            function playSound(id, volume = 1.0) {\n                if (!assets.audioContext || !assets.audioBuffers[id]) return;\n                \n                try {\n                    const source = assets.audioContext.createBufferSource();\n                    const gainNode = assets.audioContext.createGain();\n                    \n                    source.buffer = assets.audioBuffers[id];\n                    gainNode.gain.value = volume;\n                    \n                    source.connect(gainNode);\n                    gainNode.connect(assets.audioContext.destination);\n                    \n                    source.start(0);\n                } catch (error) {\n                    console.error('Error playing sound:', error);\n                }\n            }\n\n            // Game state\n            const state = {\n                attacks: [],\n                particles: [],\n                soul: {\n                    x: 0,\n                    y: 0,\n                    vx: 0,\n                    vy: 0,\n                    pulsePhase: 0,\n                    lastHitTime: 0\n                },\n                score: {\n                    attacksLaunched: 0,\n                    nearMisses: 0,\n                    damageTaken: 0\n                },\n                screenShake: {\n                    x: 0,\n                    y: 0,\n                    intensity: 0\n                },\n                slam: {\n                    active: false,\n                    direction: { x: 0, y: 0 },\n                    timer: 0,\n                    duration: 0.3\n                },\n                barrage: {\n                    active: false,\n                    timer: 0,\n                    nextSpawn: 0\n                },\n                circleBarrage: {\n                    active: false,\n                    timer: 0,\n                    nextSpawn: 0,\n                    angle: 0,\n                    centerX: 0,\n                    centerY: 0\n                },\n                sans: {\n                    x: 0,\n                    y: 0,\n                    chasing: false,\n                    lastHitTime: 0,\n                    autoAttackTimer: 0\n                },\n                raceTrack: {\n                    active: false,\n                    timer: 0,\n                    nextSpawn: 0,\n                    phase: 0\n                },\n                gravityEnabled: false,\n                shieldEnabled: false,\n                shieldRotation: 0,\n                playerControlsSoul: false,\n                soulKeys: {\n                    up: false,\n                    down: false,\n                    left: false,\n                    right: false\n                },\n                selectedObject: null,\n                isDragging: false,\n                dragOffset: { x: 0, y: 0 }\n            };\n\n            // Battle box dimensions\n            const battleBox = {\n                get width() { \n                    return state.gravityEnabled ? (window.gameConfig.arena?.width || 640) : window.gameConfig.battleBox.width; \n                },\n                get height() { \n                    return state.gravityEnabled ? (window.gameConfig.arena?.height || 220) : window.gameConfig.battleBox.height; \n                },\n                get x() { return (canvas.width - this.width) / 2; },\n                get y() { return state.gravityEnabled ? (1280 - this.height - 450) : 220; }\n            };\n\n            // Initialize soul position\n            state.soul.x = battleBox.x + battleBox.width / 2;\n            state.soul.y = battleBox.y + battleBox.height / 2;\n\n            // Initialize sans position\n            state.sans.x = battleBox.x + battleBox.width / 2;\n            state.sans.y = battleBox.y + window.gameConfig.sans.yOffset;\n\n            // Show game parameters\n            lib.showGameParameters({\n                name: \"Sans Simulator Settings\",\n                params: {\n                    \"Soul Speed\": { \n                        key: \"soul.speed\", \n                        type: \"slider\", \n                        min: 1, \n                        max: 10, \n                        step: 0.5 \n                    },\n                    \"Soul Dodge Radius\": { \n                        key: \"soul.dodgeRadius\", \n                        type: \"slider\", \n                        min: 50, \n                        max: 200, \n                        step: 10 \n                    },\n                    \"Soul Size\": { \n                        key: \"soul.size\", \n                        type: \"slider\", \n                        min: 20, \n                        max: 50, \n                        step: 2 \n                    },\n                    \"Soul Color\": { \n                        key: \"soul.color\", \n                        type: \"color\" \n                    },\n                    \"Bone Speed\": { \n                        key: \"attacks.boneSpeed\", \n                        type: \"slider\", \n                        min: 2, \n                        max: 10, \n                        step: 0.5 \n                    },\n                    \"Bone Size\": { \n                        key: \"attacks.boneSize\", \n                        type: \"slider\", \n                        min: 20, \n                        max: 60, \n                        step: 5 \n                    },\n                    \"Blaster Charge Time\": { \n                        key: \"attacks.blasterChargeTime\", \n                        type: \"slider\", \n                        min: 0.5, \n                        max: 3, \n                        step: 0.1 \n                    },\n                    \"Blaster Beam Duration\": { \n                        key: \"attacks.blasterBeamDuration\", \n                        type: \"slider\", \n                        min: 0.5, \n                        max: 2, \n                        step: 0.1 \n                    },\n                    \"Sans Chase Speed\": { \n                        key: \"sans.chaseSpeed\", \n                        type: \"slider\", \n                        min: 0.5, \n                        max: 10, \n                        step: 0.1 \n                    },\n                    \"Battle Box Color\": { \n                        key: \"visual.battleBoxColor\", \n                        type: \"color\" \n                    },\n                    \"Background Color\": { \n                        key: \"visual.backgroundColor\", \n                        type: \"color\" \n                    },\n                    \"Attack Color\": { \n                        key: \"visual.attackColor\", \n                        type: \"dropdown\", \n                        options: [\n                            { label: \"White\", value: \"white\" },\n                            { label: \"Blue\", value: \"blue\" },\n                            { label: \"Orange\", value: \"orange\" }\n                        ]\n                    },\n                    \"Particle Effects\": { \n                        key: \"visual.particleEffects\", \n                        type: \"checkbox\" \n                    },\n                    \"Screen Shake\": { \n                        key: \"visual.screenShake\", \n                        type: \"checkbox\" \n                    },\n                    \"Gravity Strength\": {\n                        key: \"soul.gravity\",\n                        type: \"slider\",\n                        min: 0.1,\n                        max: 2.0,\n                        step: 0.1\n                    },\n                    \"Jump Force\": {\n                        key: \"soul.jumpForce\",\n                        type: \"slider\",\n                        min: 5,\n                        max: 25,\n                        step: 1\n                    },\n                    \"Shield Speed\": {\n                        key: \"shield.rotationSpeed\",\n                        type: \"slider\",\n                        min: 1,\n                        max: 10,\n                        step: 0.5\n                    },\n                    \"Arena Width\": {\n                        key: \"arena.width\",\n                        type: \"slider\",\n                        min: 400,\n                        max: 700,\n                        step: 10\n                    },\n                    \"Arena Height\": {\n                        key: \"arena.height\",\n                        type: \"slider\",\n                        min: 200,\n                        max: 500,\n                        step: 10\n                    }\n                }\n            });\n            function updateSoulAI(dt) {\n                if (mode === 'edit') return;\n\n                const config = window.gameConfig.soul;\n                const soulSize = config.size;\n                const speed = config.speed * 1.5; // \"Level 25\" boost\n                const dodgeRadius = config.dodgeRadius * 1.2;\n                \n                // 0. Manual Control\n                if (state.playerControlsSoul) {\n                    let mx = 0;\n                    let my = 0;\n                    if (state.soulKeys.left) mx -= 1;\n                    if (state.soulKeys.right) mx += 1;\n                    \n                    if (state.gravityEnabled) {\n                        // Gravity physics\n                        const gravity = window.gameConfig.soul.gravity || 0.8;\n                        state.soul.vy += gravity;\n                        \n                        // Platform collisions for Manual Control\n                        let onPlatform = false;\n                        const margin = soulSize / 2;\n                        const platforms = window.gameConfig.arena?.platforms || [];\n                        \n                        // Floor check\n                        if (state.soul.y >= (battleBox.y + battleBox.height - margin)) {\n                            onPlatform = true;\n                        }\n                        \n                        // Platform check\n                        for (const p of platforms) {\n                            const px = battleBox.x + p.x;\n                            const py = battleBox.y + p.y;\n                            if (state.soul.x > px && state.soul.x < px + p.w &&\n                                state.soul.y + margin >= py && state.soul.y + margin <= py + p.h + 10 &&\n                                state.soul.vy >= 0) {\n                                state.soul.y = py - margin;\n                                state.soul.vy = 0;\n                                onPlatform = true;\n                            }\n                        }\n                        \n                        // Jump logic - using 'up' key\n                        if (onPlatform && state.soulKeys.up) {\n                            state.soul.vy = -(window.gameConfig.soul.jumpForce || 15);\n                            playSound('soul_dodge_sound', 0.4);\n                        }\n                        \n                        state.soul.vx = mx * speed;\n                    } else {\n                        // Regular 8-way movement\n                        if (state.soulKeys.up) my -= 1;\n                        if (state.soulKeys.down) my += 1;\n\n                        if (mx !== 0 || my !== 0) {\n                            const mag = Math.sqrt(mx * mx + my * my);\n                            state.soul.vx = (mx / mag) * speed;\n                            state.soul.vy = (my / mag) * speed;\n                        } else {\n                            state.soul.vx *= 0.8;\n                            state.soul.vy *= 0.8;\n                        }\n                    }\n\n                    state.soul.x += state.soul.vx;\n                    state.soul.y += state.soul.vy;\n\n                    // Hard clamp to battle box\n                    const margin = soulSize / 2;\n                    state.soul.x = Math.max(battleBox.x + margin, Math.min(battleBox.x + battleBox.width - margin, state.soul.x));\n                    state.soul.y = Math.max(battleBox.y + margin, Math.min(battleBox.y + battleBox.height - margin, state.soul.y));\n\n                    state.soul.pulsePhase += dt * 3;\n                    return;\n                }\n\n                let forceX = 0;\n                let forceY = 0;\n\n                // 0. Barrage Logic\n                if (state.barrage.active) {\n                    state.barrage.timer -= dt;\n                    state.barrage.nextSpawn -= dt;\n                    if (state.barrage.nextSpawn <= 0) {\n                        spawnBarrageBlaster(state.soul.x, state.soul.y);\n                        state.barrage.nextSpawn = 1/3; // 3 blasters per second\n                    }\n                    if (state.barrage.timer <= 0) {\n                        state.barrage.active = false;\n                    }\n                }\n\n                // 0. Circle Barrage Logic\n                if (state.circleBarrage.active) {\n                    state.circleBarrage.timer -= dt;\n                    state.circleBarrage.nextSpawn -= dt;\n                    if (state.circleBarrage.nextSpawn <= 0) {\n                        spawnSequentialCircleBlaster();\n                        state.circleBarrage.nextSpawn = 1/7; // 7 blasters per second\n                        state.circleBarrage.angle += (Math.PI * 2) / 12; // 12 blasters per full circle\n                    }\n                    if (state.circleBarrage.timer <= 0) {\n                        state.circleBarrage.active = false;\n                    }\n                }\n\n                // 0. Slam Handling (Overrides AI)\n                if (state.slam && state.slam.active) {\n                    state.slam.timer -= dt;\n                    if (state.slam.timer <= 0) {\n                        state.slam.active = false;\n                    } else {\n                        const slamStrength = 60;\n                        const sx = state.slam.direction.x * slamStrength;\n                        const sy = state.slam.direction.y * slamStrength;\n                        \n                        state.soul.vx = (state.soul.vx + sx) * 0.7;\n                        state.soul.vy = (state.soul.vy + sy) * 0.7;\n                        \n                        state.soul.x += state.soul.vx;\n                        state.soul.y += state.soul.vy;\n                        \n                        // Wall hit detection for slam\n                        const margin = soulSize / 2;\n                        const prevX = state.soul.x;\n                        const prevY = state.soul.y;\n                        \n                        state.soul.x = Math.max(battleBox.x + margin, Math.min(battleBox.x + battleBox.width - margin, state.soul.x));\n                        state.soul.y = Math.max(battleBox.y + margin, Math.min(battleBox.y + battleBox.height - margin, state.soul.y));\n                        \n                        if (state.soul.x !== prevX || state.soul.y !== prevY) {\n                            if (state.slam.timer > 0.1) { // Only shake once per slam roughly\n                                addScreenShake(8);\n                                playSound('bone_spawn_sound', 0.5);\n                                spawnParticles(state.soul.x, state.soul.y, 5);\n                            }\n                        }\n                        \n                        state.soul.pulsePhase += dt * 10;\n                        return; // Important: Normal AI is disabled during slam\n                    }\n                }\n\n                // 1. Avoid Sans (if chasing)\n                if (state.sans.chasing) {\n                    const dx = state.soul.x - state.sans.x;\n                    const dy = state.soul.y - state.sans.y;\n                    const dist = Math.sqrt(dx * dx + dy * dy);\n                    const sansAvoidRadius = 320; \n                    \n                    if (dist < sansAvoidRadius) {\n                        const weight = Math.pow((sansAvoidRadius - dist) / sansAvoidRadius, 1.5);\n                        \n                        // Primary avoidance (Move away) - balanced so other threats still matter\n                        forceX += (dx / Math.max(dist, 1)) * speed * 3.5 * weight;\n                        forceY += (dy / Math.max(dist, 1)) * speed * 3.5 * weight;\n\n                        // SIDESTEP LOGIC: Move perpendicular to Sans's approach\n                        const perpX = -dy;\n                        const perpY = dx;\n                        \n                        let side = 1;\n                        const centerX = battleBox.x + battleBox.width / 2;\n                        const centerY = battleBox.y + battleBox.height / 2;\n                        const dotToCenter = (perpX * (centerX - state.soul.x) + perpY * (centerY - state.soul.y));\n                        side = dotToCenter > 0 ? 1 : -1;\n\n                        forceX += (perpX / Math.max(dist, 1)) * side * speed * 2.5 * weight;\n                        forceY += (perpY / Math.max(dist, 1)) * side * speed * 2.5 * weight;\n                    }\n                }\n\n                // 2. Avoid Gaster Blaster Beams (Highest Priority)\n                for (const attack of state.attacks) {\n                    if ((attack.type === 'blaster' || attack.type === 'circle_blaster' || attack.type === 'barrage_blaster')) {\n                        // Check if it's firing or about to fire\n                        if (attack.phase === 'fire' || (attack.phase === 'charge' && attack.chargeTime > attack.maxChargeTime * 0.5)) {\n                            // Calculate beam line\n                            let angle = 0;\n                            if (attack.rotation !== undefined) {\n                                angle = attack.rotation;\n                            } else {\n                                angle = attack.fromLeft ? 0 : Math.PI;\n                            }\n                            \n                            const beamDirX = Math.cos(angle);\n                            const beamDirY = Math.sin(angle);\n                            \n                            // Vector from blaster to soul\n                            const dx = state.soul.x - attack.x;\n                            const dy = state.soul.y - attack.y;\n                            \n                            // Project soul onto beam line\n                            const projection = dx * beamDirX + dy * beamDirY;\n                            \n                            // If soul is in front of blaster head\n                            if (projection > 0) { \n                                const closestX = attack.x + projection * beamDirX;\n                                const closestY = attack.y + projection * beamDirY;\n                                const distToBeam = Math.sqrt((state.soul.x - closestX) ** 2 + (state.soul.y - closestY) ** 2);\n                                \n                                const beamSafetyMargin = 60 + soulSize / 2;\n                                if (distToBeam < beamSafetyMargin) {\n                                    // Calculate escape vector (perpendicular to beam)\n                                    const perpX = -beamDirY;\n                                    const perpY = beamDirX;\n                                    \n                                    const dot = (state.soul.x - closestX) * perpX + (state.soul.y - closestY) * perpY;\n                                    const side = dot > 0 ? 1 : -1;\n                                    \n                                    const weight = Math.pow((beamSafetyMargin - distToBeam) / beamSafetyMargin, 0.8);\n                                    forceX += perpX * side * speed * 8 * weight;\n                                    forceY += perpY * side * speed * 8 * weight;\n                                }\n                            }\n                        }\n                    }\n                }\n\n                // 3. Avoid Bones and Projectiles\n                for (const attack of state.attacks) {\n                    if (attack.type === 'bone' || attack.type === 'blue_bone') {\n                        const dx = state.soul.x - attack.x;\n                        const dy = state.soul.y - attack.y;\n                        const dist = Math.sqrt(dx * dx + dy * dy);\n                        \n                        const boneDodgeRadius = dodgeRadius * 1.5;\n                        if (dist < boneDodgeRadius) {\n                            const weight = Math.pow((boneDodgeRadius - dist) / boneDodgeRadius, 1.5);\n\n                            const lookAhead = 20;\n                            const predictedAttackX = attack.x + attack.vx * lookAhead;\n                            const predictedAttackY = attack.y + attack.vy * lookAhead;\n                            const pdx = state.soul.x - predictedAttackX;\n                            const pdy = state.soul.y - predictedAttackY;\n                            const pdist = Math.sqrt(pdx * pdx + pdy * pdy) || 1;\n                            \n                            const boneAvoidMultiplier = 5;\n                            forceX += (dx / Math.max(dist, 1) + pdx / pdist) * speed * weight * boneAvoidMultiplier;\n                            forceY += (dy / Math.max(dist, 1) + pdy / pdist) * speed * weight * boneAvoidMultiplier;\n                            \n                            const velMag = Math.sqrt(attack.vx * attack.vx + attack.vy * attack.vy);\n                            if (velMag > 0) {\n                                const vnx = attack.vx / velMag;\n                                const vny = attack.vy / velMag;\n                                const dotToSoul = (-dx) * vnx + (-dy) * vny; \n                                \n                                if (dotToSoul < 0) {\n                                    const cross = (dx * -vny + dy * vnx);\n                                    const side = cross > 0 ? 1 : -1;\n                                    forceX += -vny * side * speed * 4 * weight;\n                                    forceY += vnx * side * speed * 4 * weight;\n                                }\n                            }\n                        }\n                    }\n\n                    if (attack.type === 'shooter_projectile') {\n                        const dx = state.soul.x - attack.x;\n                        const dy = state.soul.y - attack.y;\n                        const dist = Math.sqrt(dx * dx + dy * dy);\n                        const dodgeRadiusProj = dodgeRadius * 1.2;\n                        \n                        if (dist < dodgeRadiusProj) {\n                            const weight = Math.pow((dodgeRadiusProj - dist) / dodgeRadiusProj, 1.5);\n                            forceX += (dx / Math.max(dist, 1)) * speed * weight * 6;\n                            forceY += (dy / Math.max(dist, 1)) * speed * weight * 6;\n                        }\n                    }\n                }\n\n                // 4. Attract to Red Bones\n                let nearestRedBone = null;\n                let minDist = Infinity;\n\n                for (const attack of state.attacks) {\n                    if (attack.type === 'red_bone' && !attack.touchedBySoul) {\n                        const dx = attack.x - state.soul.x;\n                        const dy = attack.y - state.soul.y;\n                        const dist = Math.sqrt(dx * dx + dy * dy);\n                        if (dist < minDist) {\n                            minDist = dist;\n                            nearestRedBone = attack;\n                        }\n                    }\n                }\n\n                if (nearestRedBone) {\n                    const dx = nearestRedBone.x - state.soul.x;\n                    const dy = nearestRedBone.y - state.soul.y;\n                    const dist = Math.sqrt(dx * dx + dy * dy) || 1;\n                    forceX += (dx / dist) * speed * 2;\n                    forceY += (dy / dist) * speed * 2;\n                }\n\n                // 5. Wall Avoidance\n                const wallMargin = 40;\n                if (state.soul.x < battleBox.x + wallMargin) forceX += speed * 3;\n                if (state.soul.x > battleBox.x + battleBox.width - wallMargin) forceX -= speed * 3;\n                if (state.soul.y < battleBox.y + wallMargin) forceY += speed * 3;\n                if (state.soul.y > battleBox.y + battleBox.height - wallMargin) forceY -= speed * 3;\n\n                // Apply forces with snappy acceleration\n                if (state.gravityEnabled) {\n                    // Gravity physics for AI\n                    const gravity = window.gameConfig.soul.gravity || 0.8;\n                    state.soul.vy += gravity;\n\n                    const margin = soulSize / 2;\n                    \n                    // Platform collisions for AI\n                    let onPlatform = false;\n                    const platforms = window.gameConfig.arena?.platforms || [];\n                    \n                    // Floor check\n                    if (state.soul.y >= (battleBox.y + battleBox.height - margin - 2)) {\n                        onPlatform = true;\n                    }\n                    \n                    // Platform check\n                    for (const p of platforms) {\n                        const px = battleBox.x + p.x;\n                        const py = battleBox.y + p.y;\n                        if (state.soul.x > px && state.soul.x < px + p.w &&\n                            state.soul.y + margin >= py && state.soul.y + margin <= py + p.h + 10 &&\n                            state.soul.vy >= 0) {\n                            state.soul.y = py - margin;\n                            state.soul.vy = 0;\n                            onPlatform = true;\n                        }\n                    }\n\n                    // Jump if avoidance force wants to go up significantly\n                    if (onPlatform && forceY < -speed * 0.3) {\n                        state.soul.vy = -(window.gameConfig.soul.jumpForce || 15);\n                        playSound('soul_dodge_sound', 0.4);\n                        spawnParticles(state.soul.x, state.soul.y + margin, 3);\n                    }\n\n                    // Horizontal movement from forceX\n                    state.soul.vx += (forceX - state.soul.vx) * 0.6;\n                } else if (forceX !== 0 || forceY !== 0) {\n                    const forceMag = Math.sqrt(forceX * forceX + forceY * forceY);\n                    if (forceMag > speed) {\n                        forceX = (forceX / forceMag) * speed;\n                        forceY = (forceY / forceMag) * speed;\n                    }\n                    \n                    state.soul.vx += (forceX - state.soul.vx) * 0.6;\n                    state.soul.vy += (forceY - state.soul.vy) * 0.6;\n                } else {\n                    // Return to center when safe\n                    const centerX = battleBox.x + battleBox.width / 2;\n                    const centerY = battleBox.y + battleBox.height / 2;\n                    const dx = centerX - state.soul.x;\n                    const dy = centerY - state.soul.y;\n                    const dist = Math.sqrt(dx * dx + dy * dy);\n                    \n                    if (dist > 10) {\n                        state.soul.vx += (dx / dist * speed * 0.5 - state.soul.vx) * 0.1;\n                        state.soul.vy += (dy / dist * speed * 0.5 - state.soul.vy) * 0.1;\n                    } else {\n                        state.soul.vx *= 0.8;\n                        state.soul.vy *= 0.8;\n                    }\n                }\n\n                // Update position\n                state.soul.x += state.soul.vx;\n                state.soul.y += state.soul.vy;\n\n                // Hard clamp to battle box\n                const margin = soulSize / 2;\n                state.soul.x = Math.max(battleBox.x + margin, Math.min(battleBox.x + battleBox.width - margin, state.soul.x));\n                state.soul.y = Math.max(battleBox.y + margin, Math.min(battleBox.y + battleBox.height - margin, state.soul.y));\n\n                // Update pulse animation\n                state.soul.pulsePhase += dt * 3;\n            }\n            // Attack spawning functions\n            function spawnBoneAttack() {\n                const fromLeft = Math.random() < 0.5;\n                const y = battleBox.y + Math.random() * (battleBox.height - 60);\n                \n                state.attacks.push({\n                    type: 'bone',\n                    x: fromLeft ? battleBox.x - 70 : battleBox.x + battleBox.width + 10,\n                    y: y,\n                    vx: (fromLeft ? 1 : -1) * window.gameConfig.attacks.boneSpeed,\n                    vy: 0,\n                    rotation: Math.random() * 0.2 - 0.1,\n                    rotationSpeed: (Math.random() - 0.5) * 0.05,\n                    width: 64,\n                    height: 16,\n                    asset: 'bone_small_horizontal'\n                });\n\n                state.score.attacksLaunched++;\n                updateScoreDisplay();\n                playSound('bone_spawn_sound', 0.3);\n                spawnParticles(state.attacks[state.attacks.length - 1].x, y, 5);\n            }\n            function spawnGasterBlaster() {\n                const fromLeft = Math.random() < 0.5;\n                const y = battleBox.y + Math.random() * (battleBox.height - 128);\n                \n                state.attacks.push({\n                    type: 'blaster',\n                    x: fromLeft ? battleBox.x - 140 : battleBox.x + battleBox.width + 10,\n                    y: y,\n                    vx: 0,\n                    vy: 0,\n                    rotation: 0,\n                    width: 128,\n                    height: 128,\n                    asset: 'gaster_blaster_head',\n                    phase: 'charge',\n                    chargeTime: 0,\n                    maxChargeTime: window.gameConfig.attacks.blasterChargeTime,\n                    beamDuration: window.gameConfig.attacks.blasterBeamDuration,\n                    beamTime: 0,\n                    fromLeft: fromLeft,\n                    shake: 0\n                });\n\n                state.score.attacksLaunched++;\n                updateScoreDisplay();\n                playSound('blaster_charge_sound', 0.4);\n            }\n\n            function spawnBoneWall() {\n                const numBones = 5 + Math.floor(Math.random() * 3);\n                const spacing = battleBox.width / (numBones + 1);\n                \n                for (let i = 0; i < numBones; i++) {\n                    const x = battleBox.x + spacing * (i + 1);\n                    \n                    state.attacks.push({\n                        type: 'bone',\n                        x: x,\n                        y: battleBox.y + battleBox.height + 10,\n                        vx: 0,\n                        vy: -window.gameConfig.attacks.boneSpeed,\n                        rotation: 0,\n                        rotationSpeed: 0,\n                        width: 16,\n                        height: 64,\n                        asset: 'bone_small_vertical'\n                    });\n                    \n                    spawnParticles(x, battleBox.y + battleBox.height, 3);\n                }\n\n                state.score.attacksLaunched++;\n                updateScoreDisplay();\n                playSound('bone_spawn_sound', 0.4);\n            }\n\n            function spawnBlueBones() {\n                const fromLeft = Math.random() < 0.5;\n                const y = battleBox.y + Math.random() * (battleBox.height - 60);\n                \n                state.attacks.push({\n                    type: 'blue_bone',\n                    x: fromLeft ? battleBox.x - 70 : battleBox.x + battleBox.width + 10,\n                    y: y,\n                    vx: (fromLeft ? 1 : -1) * window.gameConfig.attacks.boneSpeed,\n                    vy: 0,\n                    rotation: 0,\n                    rotationSpeed: (Math.random() - 0.5) * 0.05,\n                    width: 16,\n                    height: 64,\n                    asset: 'blue_bone_vertical'\n                });\n\n                state.score.attacksLaunched++;\n                updateScoreDisplay();\n                playSound('bone_spawn_sound', 0.3);\n                spawnParticles(state.attacks[state.attacks.length - 1].x, y, 5);\n            }\n\n            function spawnRedBones() {\n                const fromLeft = Math.random() < 0.5;\n                const y = battleBox.y + Math.random() * (battleBox.height - 60);\n                \n                state.attacks.push({\n                    type: 'red_bone',\n                    x: fromLeft ? battleBox.x - 70 : battleBox.x + battleBox.width + 10,\n                    y: y,\n                    vx: (fromLeft ? 1 : -1) * (window.gameConfig.attacks.boneSpeed * 0.8), // Slightly slower for fairness\n                    vy: 0,\n                    rotation: 0,\n                    rotationSpeed: 0,\n                    width: 16,\n                    height: 64,\n                    asset: 'bone_small_vertical',\n                    touchedBySoul: false\n                });\n\n                state.score.attacksLaunched++;\n                updateScoreDisplay();\n                playSound('bone_spawn_sound', 0.3);\n            }\n\n            function spawnSlamAttack() {\n                const directions = [\n                    { x: 1, y: 0 },  // Right\n                    { x: -1, y: 0 }, // Left\n                    { x: 0, y: 1 },  // Down\n                    { x: 0, y: -1 }  // Up\n                ];\n                const dir = directions[Math.floor(Math.random() * directions.length)];\n                \n                state.slam.active = true;\n                state.slam.direction = dir;\n                state.slam.timer = state.slam.duration;\n                \n                state.score.attacksLaunched++;\n                updateScoreDisplay();\n                playSound('soul_dodge_sound', 1.0);\n                addScreenShake(3);\n            }\n\n            function spawnBarrageBlaster(tx, ty) {\n                const angle = Math.random() * Math.PI * 2;\n                const dist = 300;\n                const x = tx + Math.cos(angle) * dist;\n                const y = ty + Math.sin(angle) * dist;\n                \n                state.attacks.push({\n                    type: 'barrage_blaster',\n                    x: x,\n                    y: y,\n                    vx: 0,\n                    vy: 0,\n                    rotation: angle + Math.PI, // Point towards target\n                    width: 96,\n                    height: 96,\n                    asset: 'gaster_blaster_head',\n                    phase: 'charge',\n                    chargeTime: 0,\n                    maxChargeTime: 0.6, // Fast charge for barrage intensity\n                    beamDuration: 0.5,\n                    beamTime: 0,\n                    shake: 0\n                });\n\n                state.score.attacksLaunched++;\n                updateScoreDisplay();\n                playSound('blaster_charge_sound', 0.3);\n            }\n\n            function updateSans(dt) {\n                const config = window.gameConfig.sans;\n                const targetX = state.sans.chasing ? state.soul.x : (battleBox.x + battleBox.width / 2);\n                const targetY = state.sans.chasing ? state.soul.y : (battleBox.y + config.yOffset);\n\n                const dx = targetX - state.sans.x;\n                const dy = targetY - state.sans.y;\n                const dist = Math.sqrt(dx * dx + dy * dy);\n\n                if (dist > 1) {\n                    const chaseFactor = state.sans.chasing ? 1.0 : 0.5; // Return slower than chase\n                    const speed = config.chaseSpeed * 100 * dt * chaseFactor;\n                    state.sans.x += (dx / dist) * Math.min(speed, dist);\n                    state.sans.y += (dy / dist) * Math.min(speed, dist);\n                }\n\n                // Auto-attack logic if player is the soul\n                if (state.playerControlsSoul && mode === 'play') {\n                    state.sans.autoAttackTimer -= dt;\n                    if (state.sans.autoAttackTimer <= 0) {\n                        const attackPool = ['bone', 'blaster', 'wall', 'blue', 'shooter', 'circle', 'slam'];\n                        const randomAttack = attackPool[Math.floor(Math.random() * attackPool.length)];\n                        handleAttackButton(randomAttack);\n                        \n                        // Set next attack timer - faster if \"chasing\" is on\n                        const baseDelay = state.sans.chasing ? 0.3 : 0.8;\n                        state.sans.autoAttackTimer = baseDelay + Math.random() * 0.7;\n                    }\n                }\n\n                // Collision with Soul\n                if (state.sans.chasing && mode === 'play') {\n                    const collisionDist = 50; // Radius for collision\n                    if (dist < collisionDist) {\n                        takeDamage(1);\n                        \n                        // Knockback\n                        const safeDist = Math.max(dist, 1);\n                        const kx = (state.soul.x - state.sans.x) / safeDist;\n                        const ky = (state.soul.y - state.sans.y) / safeDist;\n                        state.soul.vx += kx * 30;\n                        state.soul.vy += ky * 30;\n                    }\n                }\n            }\n\n            // Particle system\n            function spawnParticles(x, y, count) {\n                if (!window.gameConfig.visual.particleEffects) return;\n                \n                for (let i = 0; i < count; i++) {\n                    const angle = Math.random() * Math.PI * 2;\n                    const speed = 2 + Math.random() * 3;\n                    \n                    state.particles.push({\n                        x: x,\n                        y: y,\n                        vx: Math.cos(angle) * speed,\n                        vy: Math.sin(angle) * speed,\n                        life: 1.0,\n                        decay: 0.02 + Math.random() * 0.02,\n                        size: 2 + Math.random() * 3\n                    });\n                }\n            }\n\n            // Screen shake\n            function addScreenShake(intensity) {\n                if (!window.gameConfig.visual.screenShake) return;\n                state.screenShake.intensity = Math.max(state.screenShake.intensity, intensity);\n            }\n\n            // Update score display\n            function updateScoreDisplay() {\n                document.getElementById('attack-count').textContent = state.score.attacksLaunched;\n                document.getElementById('near-miss-count').textContent = state.score.nearMisses;\n                document.getElementById('damage-count').textContent = state.score.damageTaken;\n            }\n\n\n            function spawnCircleAttack() {\n                state.circleBarrage.active = true;\n                state.circleBarrage.timer = 6.0; \n                state.circleBarrage.nextSpawn = 0;\n                state.circleBarrage.angle = Math.random() * Math.PI * 2;\n                state.circleBarrage.centerX = state.soul.x;\n                state.circleBarrage.centerY = state.soul.y;\n\n                state.score.attacksLaunched++;\n                updateScoreDisplay();\n            }\n\n            function spawnSequentialCircleBlaster() {\n                const radius = 350;\n                const angle = state.circleBarrage.angle;\n                \n                state.attacks.push({\n                    type: 'circle_blaster',\n                    angle: angle,\n                    distance: radius,\n                    rotationSpeed: 0,\n                    width: 110,\n                    height: 110,\n                    asset: 'gaster_blaster_head',\n                    phase: 'charge',\n                    chargeTime: 0,\n                    maxChargeTime: 0.8,\n                    beamDuration: 1.8,\n                    beamTime: 0,\n                    shake: 0,\n                    centerX: state.circleBarrage.centerX,\n                    centerY: state.circleBarrage.centerY\n                });\n\n                playSound('blaster_charge_sound', 0.4);\n            }\n\n            function spawnShooterAttack() {\n                const margin = 50;\n                const sides = [\n                    { x: battleBox.x - margin, y: battleBox.y + Math.random() * battleBox.height },\n                    { x: battleBox.x + battleBox.width + margin, y: battleBox.y + Math.random() * battleBox.height },\n                    { x: battleBox.x + Math.random() * battleBox.width, y: battleBox.y - margin },\n                    { x: battleBox.x + Math.random() * battleBox.width, y: battleBox.y + battleBox.height + margin }\n                ];\n                const pos = sides[Math.floor(Math.random() * sides.length)];\n\n                state.attacks.push({\n                    type: 'shooter',\n                    x: pos.x,\n                    y: pos.y,\n                    width: 40,\n                    height: 40,\n                    shootTimer: 0,\n                    lifeTime: window.gameConfig.attacks.shooterLifeTime || 5,\n                    angle: 0\n                });\n\n                state.score.attacksLaunched++;\n                updateScoreDisplay();\n                playSound('bone_spawn_sound', 0.5);\n            }\n\n            function updateRaceTrack(dt) {\n                if (!state.raceTrack.active) return;\n                \n                state.raceTrack.timer -= dt;\n                if (state.raceTrack.timer <= 0) {\n                    state.raceTrack.active = false;\n                    return;\n                }\n                \n                state.raceTrack.phase += dt * 3;\n                state.raceTrack.nextSpawn -= dt;\n                \n                if (state.raceTrack.nextSpawn <= 0) {\n                    const speed = window.gameConfig.attacks.boneSpeed * 1.5;\n                    const amplitude = 100;\n                    const gap = 140;\n                    const centerY = battleBox.y + battleBox.height / 2 + Math.sin(state.raceTrack.phase) * amplitude;\n                    \n                    // Top track bone\n                    state.attacks.push({\n                        type: 'bone',\n                        x: battleBox.x + battleBox.width + 40,\n                        y: centerY - gap / 2 - 100,\n                        vx: -speed,\n                        vy: 0,\n                        rotation: 0,\n                        rotationSpeed: 0,\n                        width: 20,\n                        height: 200,\n                        asset: 'bone_large_vertical'\n                    });\n                    \n                    // Bottom track bone\n                    state.attacks.push({\n                        type: 'bone',\n                        x: battleBox.x + battleBox.width + 40,\n                        y: centerY + gap / 2 + 100,\n                        vx: -speed,\n                        vy: 0,\n                        rotation: 0,\n                        rotationSpeed: 0,\n                        width: 20,\n                        height: 200,\n                        asset: 'bone_large_vertical'\n                    });\n                    \n                    state.raceTrack.nextSpawn = 0.08;\n                }\n            }\n\n            // Update attacks\n            function updateAttacks(dt) {\n                for (let i = state.attacks.length - 1; i >= 0; i--) {\n                    const attack = state.attacks[i];\n                    \n                    if (attack.type === 'blaster' || attack.type === 'circle_blaster' || attack.type === 'barrage_blaster') {\n                        if (attack.type === 'circle_blaster') {\n                            // Removed rotationSpeed update to keep blasters static in the circle\n                            attack.x = attack.centerX + Math.cos(attack.angle) * attack.distance;\n                            attack.y = attack.centerY + Math.sin(attack.angle) * attack.distance;\n                            attack.rotation = attack.angle + Math.PI; \n                        }\n\n                        if (attack.phase === 'charge') {\n                            attack.chargeTime += dt;\n                            attack.shake = Math.sin(attack.chargeTime * 30) * 2;\n                            \n                            if (attack.chargeTime >= attack.maxChargeTime) {\n                                attack.phase = 'fire';\n                                attack.beamTime = 0;\n                                playSound('blaster_fire_sound', 0.5);\n                                addScreenShake(5);\n                            }\n                        } else if (attack.phase === 'fire') {\n                            attack.beamTime += dt;\n                            \n                            // Collision with soul if manually controlled\n                            if (state.playerControlsSoul) {\n                                let angle = 0;\n                                if (attack.rotation !== undefined) {\n                                    angle = attack.rotation;\n                                } else {\n                                    angle = attack.fromLeft ? 0 : Math.PI;\n                                }\n                                \n                                const beamDirX = Math.cos(angle);\n                                const beamDirY = Math.sin(angle);\n                                const dx = state.soul.x - attack.x;\n                                const dy = state.soul.y - attack.y;\n                                const projection = dx * beamDirX + dy * beamDirY;\n                                \n                                if (projection > 0) {\n                                    const closestX = attack.x + projection * beamDirX;\n                                    const closestY = attack.y + projection * beamDirY;\n                                    const distToBeam = Math.sqrt((state.soul.x - closestX) ** 2 + (state.soul.y - closestY) ** 2);\n                                    \n                                    const hitWidth = (attack.type === 'circle_blaster' || attack.type === 'barrage_blaster') ? 35 : 25;\n                                    if (distToBeam < hitWidth) {\n                                        takeDamage(2);\n                                    }\n                                }\n                            }\n\n                            if (attack.beamTime >= attack.beamDuration) {\n                                state.attacks.splice(i, 1);\n                            }\n                        }\n                    } else if (attack.type === 'shooter') {\n                        attack.lifeTime -= dt;\n                        if (attack.lifeTime <= 0) {\n                            state.attacks.splice(i, 1);\n                            continue;\n                        }\n\n                        // Face player\n                        const dx = state.soul.x - attack.x;\n                        const dy = state.soul.y - attack.y;\n                        attack.angle = Math.atan2(dy, dx);\n\n                        // Shooting logic\n                        attack.shootTimer -= dt;\n                        if (attack.shootTimer <= 0) {\n                            attack.shootTimer = 1 / (window.gameConfig.attacks.shooterFireRate || 1.5);\n                            \n                            const speed = window.gameConfig.attacks.shooterProjectileSpeed || 4;\n                            state.attacks.push({\n                                type: 'shooter_projectile',\n                                x: attack.x,\n                                y: attack.y,\n                                vx: Math.cos(attack.angle) * speed,\n                                vy: Math.sin(attack.angle) * speed,\n                                rotation: attack.angle,\n                                width: 20,\n                                height: 20\n                            });\n                            playSound('bone_spawn_sound', 0.2);\n                        }\n                    } else {\n                        // Update position\n                        attack.x += attack.vx;\n                        attack.y += attack.vy;\n                        attack.rotation += attack.rotationSpeed;\n\n                        // Shield collision\n                        if (state.shieldEnabled) {\n                            const centerX = battleBox.x + battleBox.width / 2;\n                            const centerY = battleBox.y + battleBox.height / 2;\n                            const shieldSize = window.gameConfig.shield.size / 2;\n                            const dx = attack.x - centerX;\n                            const dy = attack.y - centerY;\n                            const dist = Math.sqrt(dx * dx + dy * dy);\n                            \n                            if (dist < shieldSize) {\n                                // Destroy projectile\n                                spawnParticles(attack.x, attack.y, 5);\n                                playSound('bone_spawn_sound', 0.2);\n                                state.attacks.splice(i, 1);\n                                continue;\n                            }\n                        }\n\n                        // Collision with soul if manually controlled\n                        if (state.playerControlsSoul) {\n                            const dx = attack.x - state.soul.x;\n                            const dy = attack.y - state.soul.y;\n                            const dist = Math.sqrt(dx * dx + dy * dy);\n                            const hitRadius = (window.gameConfig.soul.size / 2) + 10;\n                            \n                            if (dist < hitRadius) {\n                                takeDamage(1);\n                                if (attack.type !== 'bone_large_vertical') { // don't delete large obstacles\n                                    state.attacks.splice(i, 1);\n                                    continue;\n                                }\n                            }\n                        }\n                        \n                        // Check if off screen\n                        // Check if off screen\n                        if (attack.x < battleBox.x - 100 || \n                            attack.x > battleBox.x + battleBox.width + 100 ||\n                            attack.y < battleBox.y - 100 || \n                            attack.y > battleBox.y + battleBox.height + 100) {\n                            \n                            // Red bone penalty: if it disappears without being touched\n                            if (attack.type === 'red_bone' && !attack.touchedBySoul) {\n                                state.score.damageTaken += 10;\n                                updateScoreDisplay();\n                                addScreenShake(10);\n                            }\n                            \n                            state.attacks.splice(i, 1);\n                            continue;\n                        }\n\n                        // Red bone collection check\n                        if (attack.type === 'red_bone' && !attack.touchedBySoul) {\n                            const dx = attack.x - state.soul.x;\n                            const dy = attack.y - state.soul.y;\n                            const dist = Math.sqrt(dx * dx + dy * dy);\n                            if (dist < (window.gameConfig.soul.size / 2 + 15)) {\n                                attack.touchedBySoul = true;\n                                spawnParticles(attack.x, attack.y, 8);\n                                playSound('soul_dodge_sound', 0.5);\n                                state.attacks.splice(i, 1); // Disappear immediately\n                                continue;\n                            }\n                        }\n                    }\n                }\n            }\n\n            // Check near misses\n            function checkNearMisses() {\n                if (mode === 'edit') return;\n\n                const soulSize = window.gameConfig.soul.size;\n                const nearMissThreshold = 30;\n                \n                for (const attack of state.attacks) {\n                    if (attack.checkedNearMiss) continue;\n                    \n                    const dx = attack.x - state.soul.x;\n                    const dy = attack.y - state.soul.y;\n                    const dist = Math.sqrt(dx * dx + dy * dy);\n                    \n                    if (dist < nearMissThreshold + soulSize / 2) {\n                        attack.checkedNearMiss = true;\n                        state.score.nearMisses++;\n                        updateScoreDisplay();\n                        playSound('soul_dodge_sound', 0.2);\n                    }\n                }\n            }\n\n            // Update particles\n            function updateParticles(dt) {\n                for (let i = state.particles.length - 1; i >= 0; i--) {\n                    const p = state.particles[i];\n                    p.x += p.vx;\n                    p.y += p.vy;\n                    p.life -= p.decay;\n                    \n                    if (p.life <= 0) {\n                        state.particles.splice(i, 1);\n                    }\n                }\n            }\n\n            // Update screen shake\n            function updateScreenShake(dt) {\n                if (state.screenShake.intensity > 0) {\n                    state.screenShake.x = (Math.random() - 0.5) * state.screenShake.intensity;\n                    state.screenShake.y = (Math.random() - 0.5) * state.screenShake.intensity;\n                    state.screenShake.intensity *= 0.9;\n                    \n                    if (state.screenShake.intensity < 0.1) {\n                        state.screenShake.intensity = 0;\n                        state.screenShake.x = 0;\n                        state.screenShake.y = 0;\n                    }\n                }\n            }\n\n            // Rendering\n            function render() {\n                ctx.save();\n                \n                // Apply screen shake\n                ctx.translate(state.screenShake.x, state.screenShake.y);\n                \n                // Background\n                ctx.fillStyle = window.gameConfig.visual.backgroundColor;\n                ctx.fillRect(0, 0, canvas.width, canvas.height);\n                \n                // Background texture (tiled)\n                if (assets.images.background_texture && window.gameConfig.visual.particleEffects) {\n                    const pattern = ctx.createPattern(assets.images.background_texture, 'repeat');\n                    ctx.fillStyle = pattern;\n                    ctx.globalAlpha = 0.3;\n                    ctx.fillRect(0, 0, canvas.width, canvas.height);\n                    ctx.globalAlpha = 1.0;\n                }\n                \n                // Background particles\n                if (window.gameConfig.visual.particleEffects) {\n                    ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';\n                    for (let i = 0; i < 20; i++) {\n                        const x = (Date.now() * 0.01 + i * 100) % canvas.width;\n                        const y = (i * 64) % canvas.height;\n                        ctx.fillRect(x, y, 2, 2);\n                    }\n                }\n                \n                // Battle box (Visible boundary)\n                ctx.strokeStyle = state.gravityEnabled ? '#00FFFF' : window.gameConfig.visual.battleBoxColor;\n                ctx.lineWidth = 4;\n                ctx.strokeRect(battleBox.x, battleBox.y, battleBox.width, battleBox.height);\n                \n                // Arena visual effects for gravity mode\n                if (state.gravityEnabled) {\n                    ctx.save();\n                    // Arena background glow\n                    const arenaGlow = ctx.createLinearGradient(battleBox.x, battleBox.y, battleBox.x, battleBox.y + battleBox.height);\n                    arenaGlow.addColorStop(0, 'rgba(0, 255, 255, 0)');\n                    arenaGlow.addColorStop(1, 'rgba(0, 255, 255, 0.1)');\n                    ctx.fillStyle = arenaGlow;\n                    ctx.fillRect(battleBox.x, battleBox.y, battleBox.width, battleBox.height);\n\n                    // Arena Title\n                    ctx.fillStyle = '#00FFFF';\n                    ctx.font = 'bold 20px Orbitron';\n                    ctx.textAlign = 'center';\n                    ctx.fillText('PLATFORMER ARENA', battleBox.x + battleBox.width / 2, battleBox.y - 15);\n\n                    // Grid\n                    ctx.globalAlpha = 0.15;\n                    ctx.strokeStyle = '#00FFFF';\n                    ctx.lineWidth = 1;\n                    for (let gx = 40; gx < battleBox.width; gx += 40) {\n                        ctx.beginPath(); ctx.moveTo(battleBox.x + gx, battleBox.y); ctx.lineTo(battleBox.x + gx, battleBox.y + battleBox.height); ctx.stroke();\n                    }\n                    for (let gy = 40; gy < battleBox.height; gy += 40) {\n                        ctx.beginPath(); ctx.moveTo(battleBox.x, battleBox.y + gy); ctx.lineTo(battleBox.x + battleBox.width, battleBox.y + gy); ctx.stroke();\n                    }\n                    ctx.restore();\n\n                    // Platforms\n                    const platforms = window.gameConfig.arena?.platforms || [];\n                    ctx.fillStyle = '#00FFFF';\n                    ctx.shadowBlur = 10;\n                    ctx.shadowColor = '#00FFFF';\n                    for (const p of platforms) {\n                        ctx.fillRect(battleBox.x + p.x, battleBox.y + p.y, p.w, p.h);\n                        // Beveled edge\n                        ctx.fillStyle = 'white';\n                        ctx.globalAlpha = 0.5;\n                        ctx.fillRect(battleBox.x + p.x, battleBox.y + p.y, p.w, 2);\n                        ctx.fillStyle = '#00FFFF';\n                        ctx.globalAlpha = 1.0;\n                    }\n                    ctx.shadowBlur = 0;\n\n                    // Floor visual\n                    ctx.beginPath();\n                    ctx.moveTo(battleBox.x, battleBox.y + battleBox.height);\n                    ctx.lineTo(battleBox.x + battleBox.width, battleBox.y + battleBox.height);\n                    ctx.strokeStyle = '#00FFFF';\n                    ctx.lineWidth = 8;\n                    ctx.stroke();\n                }\n                \n                // Shield\n                if (state.shieldEnabled) {\n                    renderShield();\n                }\n                \n                // Sans\n                renderSans();\n\n                // Attacks\n                for (const attack of state.attacks) {\n                    if (attack.type === 'blaster' || attack.type === 'circle_blaster' || attack.type === 'barrage_blaster') {\n                        renderGasterBlaster(attack);\n                    } else if (attack.type === 'shooter') {\n                        renderShooter(attack);\n                    } else if (attack.type === 'shooter_projectile') {\n                        renderShooterProjectile(attack);\n                    } else {\n                        renderBone(attack);\n                    }\n                }\n                \n                // Particles\n                for (const p of state.particles) {\n                    ctx.fillStyle = `rgba(255, 255, 255, ${p.life})`;\n                    ctx.fillRect(p.x - p.size / 2, p.y - p.size / 2, p.size, p.size);\n                }\n                \n                // Soul\n                renderSoul();\n                \n                // Edit mode selection\n                if (mode === 'edit' && state.selectedObject) {\n                    ctx.strokeStyle = 'yellow';\n                    ctx.lineWidth = 2;\n                    ctx.setLineDash([5, 5]);\n                    \n                    if (state.selectedObject.type === 'soul') {\n                        const size = window.gameConfig.soul.size;\n                        ctx.strokeRect(state.soul.x - size / 2 - 5, state.soul.y - size / 2 - 5, size + 10, size + 10);\n                    }\n                    \n                    ctx.setLineDash([]);\n                }\n                \n                ctx.restore();\n            }\n\n            function takeDamage(amount) {\n                if (mode === 'edit') return;\n                const now = Date.now();\n                if (now - state.soul.lastHitTime < 500) return; // 0.5s i-frames\n                \n                state.soul.lastHitTime = now;\n                state.score.damageTaken += amount;\n                updateScoreDisplay();\n                addScreenShake(10);\n                playSound('blaster_fire_sound', 0.4);\n                spawnParticles(state.soul.x, state.soul.y, 10);\n            }\n\n            function renderShield() {\n                const centerX = battleBox.x + battleBox.width / 2;\n                const centerY = battleBox.y + battleBox.height / 2;\n                const size = window.gameConfig.shield.size || 120;\n                \n                ctx.save();\n                ctx.translate(centerX, centerY);\n                ctx.rotate(state.shieldRotation);\n                \n                ctx.strokeStyle = window.gameConfig.shield.color || '#00FF00';\n                ctx.lineWidth = 8;\n                ctx.lineCap = 'round';\n                \n                // Draw a spinning cross shield\n                ctx.beginPath();\n                ctx.moveTo(-size/2, 0);\n                ctx.lineTo(size/2, 0);\n                ctx.moveTo(0, -size/2);\n                ctx.lineTo(0, size/2);\n                ctx.stroke();\n                \n                // Outer ring\n                ctx.setLineDash([10, 10]);\n                ctx.beginPath();\n                ctx.arc(0, 0, size/2, 0, Math.PI * 2);\n                ctx.stroke();\n                \n                ctx.restore();\n            }\n\n            function renderSans() {\n                const img = assets.images.sans_sprite;\n                if (!img) return;\n\n                const config = window.gameConfig.sans;\n                const bob = Math.sin(Date.now() / 1000 * config.animationSpeed) * config.bobIntensity;\n                \n                const sansWidth = img.width * config.scale;\n                const sansHeight = img.height * config.scale;\n                \n                const x = state.sans.x;\n                const y = state.sans.y + bob;\n\n                ctx.save();\n                ctx.translate(x, y);\n                ctx.drawImage(img, -sansWidth / 2, -sansHeight / 2, sansWidth, sansHeight);\n                ctx.restore();\n            }\n\n            function renderSoul() {\n                const size = window.gameConfig.soul.size;\n                const pulse = 1 + Math.sin(state.soul.pulsePhase) * 0.05;\n                \n                ctx.save();\n                ctx.translate(state.soul.x, state.soul.y);\n                ctx.scale(pulse, pulse);\n                \n                let soulColor = window.gameConfig.soul.color;\n                if (state.gravityEnabled) {\n                    soulColor = '#00FFFF'; // Blue soul during gravity\n                } else if (state.slam && state.slam.active) {\n                    soulColor = '#0000FF'; // Dark blue soul during slam\n                }\n\n                // Glow effect\n                if (window.gameConfig.visual.particleEffects) {\n                    const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, size);\n                    gradient.addColorStop(0, soulColor + '80');\n                    gradient.addColorStop(1, soulColor + '00');\n                    ctx.fillStyle = gradient;\n                    ctx.fillRect(-size, -size, size * 2, size * 2);\n                }\n                \n                // Draw soul\n                if (assets.images.soul_heart) {\n                    const img = assets.images.soul_heart;\n                    \n                    // Apply color tint\n                    if (soulColor !== '#FF0000') {\n                        ctx.globalCompositeOperation = 'multiply';\n                        ctx.fillStyle = soulColor;\n                        ctx.fillRect(-size / 2, -size / 2, size, size);\n                        ctx.globalCompositeOperation = 'destination-in';\n                    }\n                    \n                    ctx.drawImage(img, -size / 2, -size / 2, size, size);\n                    ctx.globalCompositeOperation = 'source-over';\n                }\n                \n                ctx.restore();\n            }\n\n            function renderBone(attack) {\n                const img = assets.images[attack.asset];\n                if (!img && attack.type !== 'shooter_projectile') return;\n                \n                ctx.save();\n                ctx.translate(attack.x, attack.y);\n                ctx.rotate(attack.rotation);\n                \n                if (attack.type === 'shooter_projectile') {\n                    ctx.fillStyle = '#00FF00';\n                    ctx.fillRect(-attack.width / 2, -attack.height / 2, attack.width, attack.height);\n                    ctx.fillStyle = 'red';\n                    ctx.fillRect(attack.width / 4, -attack.height / 4, attack.width / 4, attack.height / 2);\n                } else {\n                    // Apply attack color tint\n                    const colorMap = {\n                        'white': '#FFFFFF',\n                        'blue': '#00FFFF',\n                        'orange': '#FFA500'\n                    };\n                    let tintColor = colorMap[window.gameConfig.visual.attackColor] || '#FFFFFF';\n                    \n                    if (attack.type === 'red_bone') tintColor = '#FF0000';\n\n                    if (attack.type === 'blue_bone' || attack.type === 'red_bone' || tintColor !== '#FFFFFF') {\n                        ctx.globalCompositeOperation = 'multiply';\n                        ctx.fillStyle = attack.type === 'blue_bone' ? '#00FFFF' : (attack.type === 'red_bone' ? '#FF0000' : tintColor);\n                        ctx.fillRect(-attack.width / 2, -attack.height / 2, attack.width, attack.height);\n                        ctx.globalCompositeOperation = 'destination-in';\n                    }\n                    \n                    ctx.drawImage(img, -attack.width / 2, -attack.height / 2, attack.width, attack.height);\n                    ctx.globalCompositeOperation = 'source-over';\n                }\n                \n                ctx.restore();\n            }\n\n            function renderShooter(attack) {\n                ctx.save();\n                ctx.translate(attack.x, attack.y);\n                ctx.rotate(attack.angle);\n\n                // Green square body\n                ctx.fillStyle = '#00FF00';\n                ctx.fillRect(-attack.width / 2, -attack.height / 2, attack.width, attack.height);\n                ctx.strokeStyle = '#8B0000';\n                ctx.lineWidth = 3;\n                ctx.strokeRect(-attack.width / 2, -attack.height / 2, attack.width, attack.height);\n\n                // Red eyes\n                ctx.fillStyle = 'red';\n                // Eyes face towards the rotation\n                ctx.fillRect(attack.width / 6, -attack.height / 4, attack.width / 4, attack.height / 8);\n                ctx.fillRect(attack.width / 6, attack.height / 8, attack.width / 4, attack.height / 8);\n\n                ctx.restore();\n            }\n\n            function renderShooterProjectile(attack) {\n                ctx.save();\n                ctx.translate(attack.x, attack.y);\n                ctx.rotate(attack.rotation);\n                ctx.fillStyle = '#00FF00';\n                ctx.fillRect(-attack.width / 2, -attack.height / 2, attack.width, attack.height);\n                ctx.fillStyle = 'red';\n                ctx.fillRect(0, -2, attack.width / 2, 4); // Small red core\n                ctx.restore();\n            }\n\n            function renderGasterBlaster(attack) {\n                const img = assets.images[attack.asset];\n                if (!img) return;\n                \n                ctx.save();\n                \n                // Draw blaster head\n                ctx.translate(attack.x + attack.shake, attack.y);\n                \n                if (attack.rotation !== undefined) {\n                    ctx.rotate(attack.rotation);\n                } else if (!attack.fromLeft) {\n                    ctx.scale(-1, 1);\n                }\n                \n                // Charge glow\n                if (attack.phase === 'charge') {\n                    const glowIntensity = attack.chargeTime / attack.maxChargeTime;\n                    ctx.shadowColor = '#0088FF';\n                    ctx.shadowBlur = 20 * glowIntensity;\n                }\n                \n                ctx.drawImage(img, -attack.width / 2, -attack.height / 2, attack.width, attack.height);\n                \n                ctx.restore();\n                \n                // Draw beam\n                if (attack.phase === 'fire') {\n                    ctx.save();\n                    ctx.translate(attack.x, attack.y);\n                    \n                    let angle = 0;\n                    if (attack.rotation !== undefined) {\n                        angle = attack.rotation;\n                    } else {\n                        angle = attack.fromLeft ? 0 : Math.PI;\n                    }\n                    ctx.rotate(angle);\n\n                    const beamWidth = (attack.type === 'circle_blaster' || attack.type === 'barrage_blaster') ? 60 : 40;\n                    const beamLength = 1000;\n                    const beamX = attack.width / 3;\n                    const beamY = -beamWidth / 2;\n                    \n                    // Pulsating beam effect\n                    const pulse = 1 + Math.sin(Date.now() / 50) * 0.1;\n                    const currentWidth = beamWidth * pulse;\n                    \n                    // Beam glow\n                    const gradient = ctx.createLinearGradient(beamX, -currentWidth/2, beamX, currentWidth/2);\n                    gradient.addColorStop(0, 'rgba(170, 221, 255, 0)');\n                    gradient.addColorStop(0.5, 'rgba(170, 221, 255, 1)');\n                    gradient.addColorStop(1, 'rgba(170, 221, 255, 0)');\n                    \n                    ctx.fillStyle = gradient;\n                    ctx.fillRect(beamX, -currentWidth/2, beamLength, currentWidth);\n                    \n                    // Beam core\n                    ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';\n                    ctx.fillRect(beamX, -currentWidth/4, beamLength, currentWidth / 2);\n                    \n                    ctx.restore();\n                }\n            }\n\n            // Input handling\n            const activeButtons = new Set();\n            let isTouchDraggingSoul = false;\n\n            canvas.addEventListener('mousedown', (e) => {\n                if (mode === 'play' && state.playerControlsSoul) {\n                    isTouchDraggingSoul = true;\n                    updateSoulPositionFromInput(e.clientX, e.clientY);\n                }\n                if (mode !== 'edit') return;\n                \n                const rect = canvas.getBoundingClientRect();\n                const x = (e.clientX - rect.left) * (canvas.width / rect.width);\n                const y = (e.clientY - rect.top) * (canvas.height / rect.height);\n                \n                // Check if clicking on soul\n                const soulSize = window.gameConfig.soul.size;\n                const dx = x - state.soul.x;\n                const dy = y - state.soul.y;\n                \n                if (Math.abs(dx) < soulSize / 2 && Math.abs(dy) < soulSize / 2) {\n                    state.selectedObject = { type: 'soul' };\n                    state.isDragging = true;\n                    state.dragOffset = { x: dx, y: dy };\n                } else {\n                    state.selectedObject = null;\n                }\n            });\n            canvas.addEventListener('mousemove', (e) => {\n                if (mode === 'play' && state.playerControlsSoul && isTouchDraggingSoul) {\n                    updateSoulPositionFromInput(e.clientX, e.clientY);\n                }\n                if (mode !== 'edit' || !state.isDragging) return;\n                \n                const rect = canvas.getBoundingClientRect();\n                const x = (e.clientX - rect.left) * (canvas.width / rect.width);\n                const y = (e.clientY - rect.top) * (canvas.height / rect.height);\n                \n                if (state.selectedObject && state.selectedObject.type === 'soul') {\n                    state.soul.x = x - state.dragOffset.x;\n                    state.soul.y = y - state.dragOffset.y;\n                    \n                    // Keep in bounds\n                    const margin = window.gameConfig.soul.size / 2;\n                    state.soul.x = Math.max(battleBox.x + margin, Math.min(battleBox.x + battleBox.width - margin, state.soul.x));\n                    state.soul.y = Math.max(battleBox.y + margin, Math.min(battleBox.y + battleBox.height - margin, state.soul.y));\n                }\n            });\n\n            canvas.addEventListener('mouseup', () => {\n                state.isDragging = false;\n                isTouchDraggingSoul = false;\n            });\n\n            // Touch handling for edit mode and play mode soul control\n            canvas.addEventListener('touchstart', (e) => {\n                if (mode === 'play' && state.playerControlsSoul) {\n                    e.preventDefault();\n                    isTouchDraggingSoul = true;\n                    const touch = e.touches[0];\n                    updateSoulPositionFromInput(touch.clientX, touch.clientY);\n                }\n                if (mode !== 'edit') return;\n                \n                const touch = e.touches[0];\n                const rect = canvas.getBoundingClientRect();\n                const x = (touch.clientX - rect.left) * (canvas.width / rect.width);\n                const y = (touch.clientY - rect.top) * (canvas.height / rect.height);\n                \n                const soulSize = window.gameConfig.soul.size;\n                const dx = x - state.soul.x;\n                const dy = y - state.soul.y;\n                \n                if (Math.abs(dx) < soulSize / 2 && Math.abs(dy) < soulSize / 2) {\n                    state.selectedObject = { type: 'soul' };\n                    state.isDragging = true;\n                    state.dragOffset = { x: dx, y: dy };\n                }\n            });\n\n            canvas.addEventListener('touchmove', (e) => {\n                if (mode === 'play' && state.playerControlsSoul && isTouchDraggingSoul) {\n                    e.preventDefault();\n                    const touch = e.touches[0];\n                    updateSoulPositionFromInput(touch.clientX, touch.clientY);\n                }\n                if (mode !== 'edit' || !state.isDragging) return;\n                \n                const touch = e.touches[0];\n                const rect = canvas.getBoundingClientRect();\n                const x = (touch.clientX - rect.left) * (canvas.width / rect.width);\n                const y = (touch.clientY - rect.top) * (canvas.height / rect.height);\n                \n                if (state.selectedObject && state.selectedObject.type === 'soul') {\n                    state.soul.x = x - state.dragOffset.x;\n                    state.soul.y = y - state.dragOffset.y;\n                    \n                    const margin = window.gameConfig.soul.size / 2;\n                    state.soul.x = Math.max(battleBox.x + margin, Math.min(battleBox.x + battleBox.width - margin, state.soul.x));\n                    state.soul.y = Math.max(battleBox.y + margin, Math.min(battleBox.y + battleBox.height - margin, state.soul.y));\n                }\n            });\n\n            canvas.addEventListener('touchend', () => {\n                state.isDragging = false;\n                isTouchDraggingSoul = false;\n            });\n\n            function updateSoulPositionFromInput(clientX, clientY) {\n                const rect = canvas.getBoundingClientRect();\n                const x = (clientX - rect.left) * (canvas.width / rect.width);\n                const y = (clientY - rect.top) * (canvas.height / rect.height);\n                \n                // Direct jump to position (ideal for soul control)\n                state.soul.x = x;\n                state.soul.y = y;\n                \n                // Clamp will happen in game loop\n            }\n\n            function handleAttackButton(attackType) {\n                initAudio();\n                \n                if (mode === 'edit') return;\n                \n                switch (attackType) {\n                    case 'bone':\n                        spawnBoneAttack();\n                        break;\n                    case 'blaster':\n                        spawnGasterBlaster();\n                        break;\n                    case 'wall':\n                        spawnBoneWall();\n                        break;\n                    case 'blue':\n                        spawnBlueBones();\n                        break;\n                    case 'circle':\n                        spawnCircleAttack();\n                        break;\n                    case 'red':\n                        spawnRedBones();\n                        break;\n                    case 'slam':\n                        spawnSlamAttack();\n                        break;\n                    case 'barrage':\n                        state.barrage.active = true;\n                        state.barrage.timer = 5.0;\n                        state.barrage.nextSpawn = 0;\n                        break;\n                    case 'chase':\n                        state.sans.chasing = !state.sans.chasing;\n                        if (state.sans.chasing) {\n                            playSound('soul_dodge_sound', 1.0);\n                        }\n                        break;\n                    case 'shooter':\n                        spawnShooterAttack();\n                        break;\n                    case 'track':\n                        state.raceTrack.active = true;\n                        state.raceTrack.timer = 8.0;\n                        state.raceTrack.nextSpawn = 0;\n                        state.raceTrack.phase = 0;\n                        break;\n                    case 'gravity':\n                        state.gravityEnabled = !state.gravityEnabled;\n                        document.getElementById('label-gravity').textContent = state.gravityEnabled ? \"Gravity ON\" : \"Gravity OFF\";\n                        document.getElementById('btn-gravity').style.borderColor = state.gravityEnabled ? \"#00FFFF\" : \"rgba(255, 255, 255, 0.4)\";\n                        playSound('soul_dodge_sound', 1.0);\n                        break;\n                    case 'shield':\n                        state.shieldEnabled = !state.shieldEnabled;\n                        document.getElementById('label-shield').textContent = state.shieldEnabled ? \"Shield ON\" : \"Shield OFF\";\n                        document.getElementById('btn-shield').style.borderColor = state.shieldEnabled ? \"#00FF00\" : \"rgba(255, 255, 255, 0.4)\";\n                        playSound('soul_dodge_sound', 1.0);\n                        break;\n                    case 'role':\n                        state.playerControlsSoul = !state.playerControlsSoul;\n                        const label = document.getElementById('label-role');\n                        const btn = document.getElementById('btn-role');\n                        if (state.playerControlsSoul) {\n                            label.textContent = \"Soul\";\n                            btn.style.borderColor = \"#FF0000\";\n                            btn.style.background = \"rgba(255, 0, 0, 0.2)\";\n                            state.sans.autoAttackTimer = 0.5;\n                        } else {\n                            label.textContent = \"Sans\";\n                            btn.style.borderColor = \"rgba(255, 255, 255, 0.4)\";\n                            btn.style.background = \"rgba(255, 255, 255, 0.08)\";\n                        }\n                        playSound('soul_dodge_sound', 1.0);\n                        break;\n                }\n            }\n\n            // Touch controls\n            const attackButtons = document.querySelectorAll('.attack-button');\n            attackButtons.forEach(button => {\n                button.addEventListener('touchstart', (e) => {\n                    e.preventDefault();\n                    const attackType = button.dataset.attack;\n                    handleAttackButton(attackType);\n                });\n                \n                button.addEventListener('click', (e) => {\n                    const attackType = button.dataset.attack;\n                    handleAttackButton(attackType);\n                });\n            });\n\n            // Reset button\n            document.getElementById('reset-btn').addEventListener('click', () => {\n                state.attacks = [];\n                state.particles = [];\n                state.score.attacksLaunched = 0;\n                state.score.nearMisses = 0;\n                state.score.damageTaken = 0;\n                state.soul.x = battleBox.x + battleBox.width / 2;\n                state.soul.y = battleBox.y + battleBox.height / 2;\n                state.soul.vx = 0;\n                state.soul.vy = 0;\n                state.gravityEnabled = false;\n                state.shieldEnabled = false;\n                document.getElementById('label-gravity').textContent = \"Gravity OFF\";\n                document.getElementById('btn-gravity').style.borderColor = \"rgba(255, 255, 255, 0.4)\";\n                document.getElementById('label-shield').textContent = \"Shield OFF\";\n                document.getElementById('btn-shield').style.borderColor = \"rgba(255, 255, 255, 0.4)\";\n                updateScoreDisplay();\n            });\n\n            // Tab switching logic\n            document.getElementById('tab-attacks-btn').addEventListener('click', () => {\n                document.getElementById('tab-attacks-btn').classList.add('active');\n                document.getElementById('tab-modes-btn').classList.remove('active');\n                document.getElementById('group-attacks').classList.add('active');\n                document.getElementById('group-modes').classList.remove('active');\n            });\n            document.getElementById('tab-modes-btn').addEventListener('click', () => {\n                document.getElementById('tab-modes-btn').classList.add('active');\n                document.getElementById('tab-attacks-btn').classList.remove('active');\n                document.getElementById('group-modes').classList.add('active');\n                document.getElementById('group-attacks').classList.remove('active');\n            });\n\n            // Keyboard controls\n            window.addEventListener('keydown', (e) => {\n                if (activeButtons.has(e.key)) return;\n                activeButtons.add(e.key);\n                \n                switch (e.key.toLowerCase()) {\n                    case 'w':\n                    case 'arrowup':\n                        state.soulKeys.up = true;\n                        break;\n                    case 's':\n                    case 'arrowdown':\n                        state.soulKeys.down = true;\n                        break;\n                    case 'a':\n                    case 'arrowleft':\n                        state.soulKeys.left = true;\n                        break;\n                    case 'd':\n                    case 'arrowright':\n                        state.soulKeys.right = true;\n                        break;\n                    case 'q':\n                        handleAttackButton('role');\n                        break;\n                    case 'j':\n                    case '1':\n                        handleAttackButton('bone');\n                        break;\n                    case 'k':\n                    case '2':\n                        handleAttackButton('blaster');\n                        break;\n                    case 'l':\n                    case '3':\n                        handleAttackButton('wall');\n                        break;\n                    case ';':\n                    case '4':\n                        handleAttackButton('blue');\n                        break;\n                    case 'm':\n                    case '5':\n                        handleAttackButton('circle');\n                        break;\n                    case 'n':\n                    case '6':\n                        handleAttackButton('red');\n                        break;\n                    case 'b':\n                    case '7':\n                        handleAttackButton('slam');\n                        break;\n                    case 'v':\n                    case '8':\n                        handleAttackButton('barrage');\n                        break;\n                    case 'c':\n                    case '9':\n                        handleAttackButton('chase');\n                        break;\n                    case 'x':\n                    case '0':\n                        handleAttackButton('shooter');\n                        break;\n                    case 't':\n                        handleAttackButton('track');\n                        break;\n                    case ' ':\n                        const attacks = ['bone', 'blaster', 'wall', 'blue', 'circle', 'red', 'slam', 'barrage'];\n                        handleAttackButton(attacks[Math.floor(Math.random() * attacks.length)]);\n                        break;\n                    case 'r':\n                        document.getElementById('reset-btn').click();\n                        break;\n                }\n            });\n\n            window.addEventListener('keyup', (e) => {\n                activeButtons.delete(e.key);\n                switch (e.key.toLowerCase()) {\n                    case 'w':\n                    case 'arrowup':\n                        state.soulKeys.up = false;\n                        break;\n                    case 's':\n                    case 'arrowdown':\n                        state.soulKeys.down = false;\n                        break;\n                    case 'a':\n                    case 'arrowleft':\n                        state.soulKeys.left = false;\n                        break;\n                    case 'd':\n                    case 'arrowright':\n                        state.soulKeys.right = false;\n                        break;\n                }\n            });\n\n            // Game loop\n            let lastTime = performance.now();\n            \n            function gameLoop(timestamp) {\n                let dt = (timestamp - lastTime) / 1000;\n                lastTime = timestamp;\n                \n                // Cap delta time\n                dt = Math.min(dt, 0.11);\n                \n                // Update\n                if (mode === 'play') {\n                    updateSoulAI(dt);\n                    updateSans(dt);\n                    updateRaceTrack(dt);\n                    updateAttacks(dt);\n                    \n                    // Final safety clamp for the soul after all systems have updated\n                    const soulSize = window.gameConfig.soul.size;\n                    const margin = soulSize / 2;\n                    state.soul.x = Math.max(battleBox.x + margin, Math.min(battleBox.x + battleBox.width - margin, state.soul.x));\n                    state.soul.y = Math.max(battleBox.y + margin, Math.min(battleBox.y + battleBox.height - margin, state.soul.y));\n\n                    checkNearMisses();\n                } else if (mode === 'edit' && !state.isDragging) {\n                    // Ensure soul stays in box even if resized in edit mode\n                    const soulSize = window.gameConfig.soul.size;\n                    const margin = soulSize / 2;\n                    state.soul.x = Math.max(battleBox.x + margin, Math.min(battleBox.x + battleBox.width - margin, state.soul.x));\n                    state.soul.y = Math.max(battleBox.y + margin, Math.min(battleBox.y + battleBox.height - margin, state.soul.y));\n                }\n                updateParticles(dt);\n                updateScreenShake(dt);\n                \n                // Update shield\n                if (state.shieldEnabled) {\n                    state.shieldRotation += (window.gameConfig.shield.rotationSpeed || 3) * dt;\n                }\n                \n                // Render\n                render();\n                \n                requestAnimationFrame(gameLoop);\n            }\n            \n            requestAnimationFrame(gameLoop);\n        }\n    </script>\n</body>\n</html>", "game_config": "{\n  \"soul\": {\n    \"speed\": 8,\n    \"dodgeRadius\": 150,\n    \"size\": 40,\n    \"color\": \"#FF0000\",\n    \"gravity\": 0.8,\n    \"jumpForce\": 15\n  },\n  \"attacks\": {\n    \"boneSpeed\": 5,\n    \"boneSize\": 40,\n    \"blasterChargeTime\": 1.5,\n    \"blasterBeamDuration\": 1.0,\n    \"shooterFireRate\": 1.5,\n    \"shooterProjectileSpeed\": 4,\n    \"shooterLifeTime\": 5\n  },\n  \"visual\": {\n    \"battleBoxColor\": \"#FFFFFF\",\n    \"backgroundColor\": \"#000000\",\n    \"attackColor\": \"white\",\n    \"particleEffects\": true,\n    \"screenShake\": true\n  },\n  \"battleBox\": {\n    \"width\": 400,\n    \"height\": 400\n  },\n  \"sans\": {\n    \"scale\": 0.4,\n    \"bobIntensity\": 3,\n    \"animationSpeed\": 2,\n    \"yOffset\": -110,\n    \"chaseSpeed\": 2\n  },\n  \"shield\": {\n    \"rotationSpeed\": 3,\n    \"size\": 120,\n    \"color\": \"#00FF00\"\n  },\n  \"arena\": {\n    \"platforms\": [\n      {\n        \"y\": 140,\n        \"w\": 120,\n        \"x\": 80,\n        \"h\": 15\n      },\n      {\n        \"h\": 15,\n        \"x\": 440,\n        \"w\": 120,\n        \"y\": 140\n      },\n      {\n        \"h\": 15,\n        \"y\": 70,\n        \"x\": 250,\n        \"w\": 140\n      }\n    ],\n    \"width\": 640,\n    \"height\": 240\n  }\n}", "game_design_document": "## Core Game Loop and Mechanics\n\n**Sans Simulator** flips the traditional Undertale battle system on its head. Instead of playing as the human soul dodging attacks, you play as Sans with unlimited power, trying to overwhelm an impossibly agile AI-controlled heart.\n\n### Moment-to-Moment Gameplay\n- Player taps attack buttons to spawn bones and Gaster Blasters\n- Each attack type has unique properties (speed, size, direction, damage potential)\n- The red heart soul moves intelligently within the battle box, dodging everything\n- Player tries to create attack patterns that corner or overwhelm the soul\n- Visual feedback shows near-misses and the soul's evasion patterns\n\n### Progression and Engagement\n- The challenge comes from trying to land even a single hit on the perfect-dodging soul\n- Players experiment with different attack combinations and timing\n- The satisfaction is in creating increasingly complex attack patterns\n- Score tracks how many attacks you've launched and how close you've come to hitting\n\n### Win/Lose Conditions\n- **No traditional win condition** - the soul dodges perfectly forever\n- The game is about the spectacle and experimentation\n- Players can reset at any time to try new strategies\n- High score based on attack density and near-misses\n\n### What Makes It Compelling\n- Power fantasy of being Sans with unlimited attacks\n- Mesmerizing visual patterns from overlapping attacks\n- Experimentation with different attack combinations\n- The impossible challenge creates a unique \"you can't win but it's fun trying\" experience\n\n## Starting Scope\n\n**First Iteration Must Include:**\n- Fixed battle box (the rectangular area where combat happens)\n- Red heart soul that moves and dodges intelligently\n- 3-4 basic attack buttons:\n  - **Bone Attack**: Spawns bones that move across the screen\n  - **Gaster Blaster**: Spawns the iconic skull that charges and fires a beam\n  - **Bone Wall**: Creates a line of bones\n  - **Blue Bones**: Bones that only hurt if you're moving (visual variety)\n- Simple AI that makes the soul dodge toward safe spaces\n- Attack spawning with no cooldowns\n- Basic particle effects for attacks\n- Score counter showing attacks launched\n- Reset button\n\n**Leave for Later Iterations:**\n- Multiple difficulty levels for the soul's dodging\n- More attack types (bone circles, platforms, etc.)\n- Combo system or attack patterns\n- Sound effects and music\n- Special attacks or ultimate moves\n- Battle box modifications (gravity changes, size changes)\n\n## Game Perspective\n\n**Fixed-screen** perspective with a centered battle box, similar to Undertale's combat view:\n- Battle box is a white-bordered rectangle in the center of the screen\n- Sans (player) is represented by attack buttons at the bottom\n- The red heart soul moves within the battle box\n- Attacks spawn from edges or specific points and move through the box\n- Black background outside the battle box\n- All action contained within the visible screen\n\n## Game World and Level Design\n\n### Battle Arena\n- **Battle Box**: A 400x400 pixel white-bordered rectangle centered in the upper portion of the screen\n- **Background**: Black void with subtle particle effects (floating white dots)\n- **Soul Starting Position**: Center of the battle box\n- **Attack Spawn Points**: \n  - Bones spawn from left/right edges\n  - Gaster Blasters spawn at top corners\n  - Bone walls spawn from bottom\n  - Blue bones spawn from random edges\n\n### Visual Layout (720x1280 canvas)\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502   [Score Display]       \u2502 (Top 100px)\n\u2502                         \u2502\n\u2502   \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510     \u2502\n\u2502   \u2502               \u2502     \u2502\n\u2502   \u2502  Battle Box   \u2502     \u2502 (400x400px centered)\n\u2502   \u2502   \u2764\ufe0f (soul)   \u2502     \u2502\n\u2502   \u2502               \u2502     \u2502\n\u2502   \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518     \u2502\n\u2502                         \u2502\n\u2502   [Attack Buttons]      \u2502 (Bottom 400px)\n\u2502   [\ud83e\uddb4] [\ud83d\udc80] [\u2593] [\ud83d\udd35]    \u2502\n\u2502                         \u2502\n\u2502   [Reset Button]        \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n### Single Level Design\nSince this is a single-screen battle simulator, there's only one \"level\":\n- The battle box remains constant\n- Challenge comes from player experimentation\n- No progression through stages\n- Endless mode by default\n\n## Edit Mode Design\n\nEdit mode allows players to customize the battle experience and visual style.\n\n### Editable Properties\n\n**Soul Behavior:**\n- `soulSpeed`: How fast the soul moves (1-10 scale)\n- `soulDodgeRadius`: How far ahead the soul \"sees\" attacks (50-200 pixels)\n- `soulSize`: Size of the heart (20-50 pixels)\n\n**Attack Properties:**\n- `boneSpeed`: Speed of bone attacks (2-10)\n- `boneSize`: Size of bone projectiles (20-60 pixels)\n- `blasterChargeTime`: How long Gaster Blasters take to fire (0.5-3 seconds)\n- `blasterBeamDuration`: How long the beam lasts (0.5-2 seconds)\n\n**Visual Customization:**\n- `battleBoxColor`: Border color of the battle box (hex color)\n- `backgroundColor`: Background color (hex color)\n- `soulColor`: Color of the heart (default red, but customizable)\n- `attackColor`: Color tint for attacks (white/blue/orange options)\n\n**Game Settings:**\n- `particleEffects`: Enable/disable background particles (boolean)\n- `screenShake`: Enable/disable screen shake on attacks (boolean)\n\n### Constraints\n- Soul speed capped to prevent it from being too slow (boring) or too fast (invisible)\n- Dodge radius must be reasonable to maintain the \"perfect dodging\" feel\n- Attack speeds have minimums to prevent frozen projectiles\n- Colors must have sufficient contrast for visibility\n\n## Control Design\n\n**Sans Simulator** uses a simple tap-based control scheme optimized for mobile, with keyboard support for desktop.\n\n### Mobile Controls (Primary)\n\n**Attack Buttons** (Bottom half of screen):\n- Four large, thumb-friendly buttons (150x150px each) arranged in a 2x2 grid\n- Each button spawns a specific attack type when tapped\n- No cooldowns - tap as fast as you want\n- Visual feedback: button scales down slightly on press, attack spawns with particle burst\n\n**Button Layout:**\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502  Bone   \u2502 Gaster  \u2502\n\u2502  Attack \u2502 Blaster \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502  Bone   \u2502  Blue   \u2502\n\u2502  Wall   \u2502  Bones  \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n**Reset Button:**\n- Small button in top-right corner (80x60px)\n- Clears all attacks and resets score\n\n### Desktop Controls (Secondary)\n\n**Keyboard:**\n- `J` or `1`: Bone Attack\n- `K` or `2`: Gaster Blaster\n- `L` or `3`: Bone Wall\n- `;` or `4`: Blue Bones\n- `R`: Reset game\n- `Space`: Random attack\n\n**Mouse:**\n- Click attack buttons (same as touch)\n- Click reset button\n\n### Input Handling\n- Track all active touches by ID to support multi-finger tapping\n- Prevent default touch behaviors to avoid scrolling\n- Clear all inputs on window blur\n- Show visual feedback for all button presses\n- No complex gestures needed - just taps\n\n### Control Philosophy\n- **Spam-friendly**: No cooldowns means players can tap rapidly\n- **Accessible**: Large buttons, simple actions\n- **Satisfying**: Immediate visual and gameplay feedback\n- **No aiming required**: Attacks spawn in predetermined patterns\n\n## Art Style and Visual Theme\n\n**Undertale-Inspired Pixel Art** with a dark, dramatic aesthetic.\n\n### Visual Style\n- **Pixel art** aesthetic matching Undertale's style\n- **High contrast**: White/light elements on black backgrounds\n- **Glowing effects**: Attacks have subtle glow/bloom\n- **Minimalist**: Clean, readable sprites without excessive detail\n- **Dramatic**: Screen shake and particle effects emphasize power\n\n### Color Palette\n- **Background**: Pure black (#000000)\n- **Battle Box**: White border (#FFFFFF)\n- **Soul**: Bright red (#FF0000) with subtle glow\n- **Bones**: White (#FFFFFF) with light blue tint (#E0F0FF)\n- **Blue Bones**: Cyan (#00FFFF)\n- **Gaster Blasters**: White/gray (#CCCCCC) with glowing blue eyes (#0088FF)\n- **Beam**: Bright white-blue (#AADDFF) with intense glow\n- **UI Text**: White (#FFFFFF)\n- **Particles**: White with varying opacity\n\n### Asset Sizes and Scale\n- **Soul**: 32x32 pixels (scales to 40x40 on screen)\n- **Small Bone**: 16x64 pixels (vertical) or 64x16 pixels (horizontal)\n- **Large Bone**: 32x128 pixels\n- **Gaster Blaster**: 128x128 pixels (head), beam is procedural\n- **Battle Box**: 400x400 pixels (drawn with code)\n- **Buttons**: 150x150 pixels each\n\n### Rendering Approach\n- **Code-rendered** (CSS/Canvas):\n  - Battle box border (simple rectangle)\n  - Score display (text)\n  - Particle effects (circles)\n  - Gaster Blaster beams (rectangles with gradient)\n  - Screen shake effect\n\n- **Generated Assets**:\n  - Soul (heart sprite)\n  - Bone sprites (various sizes)\n  - Gaster Blaster head\n  - Button icons\n  - Background texture (optional subtle pattern)\n\n### Animation and Effects\n- Soul pulses gently (scale 0.95-1.05)\n- Bones rotate slightly as they move\n- Gaster Blasters shake during charge-up\n- Screen shakes on beam fire\n- Particle burst on attack spawn\n- Smooth movement for all projectiles\n\n### Consistency Notes\n- All sprites should have clean pixel edges (no anti-aliasing blur)\n- Maintain consistent pixel density across all assets\n- Use same viewing angle (straight-on 2D) for all elements\n- Keep lighting consistent (top-down light source)\n- All attacks should feel like they belong to the same \"Sans attack\" family", "persistent_storage_config": null, "game_metadata": {"game_id": "01KS3TY6QZSC13T5F7Q63V4S3H", "title": "Sans Simulator", "description": "**Description:** Play as Sans in an inverted bullet-hell where you unleash unlimited bones and Gaster Blasters against a hyper-intelligent, auto-dodging soul.", "thumbnail_url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/thumbnail_4d33dd16-b657-4109-b38c-0f0aefbfa093.webp", "created_at": 1779319290.0, "updated_at": 1779677432145.984, "art_style": null}}