From 561343164f0b578a29c8a93447ecda9b85fea1bf Mon Sep 17 00:00:00 2001 From: Dorian Date: Sun, 8 Mar 2026 21:29:02 +0000 Subject: [PATCH] feat: 30 Bitcoin-themed choreographies lightningBolt, hashSmash, blockDrop, mempoolFlood, difficultyBomb, feeSpike, utxoScatter, nodeSync, relayBounce, channelForceClose, halvingStrike, coldStorage, paperWalletSlash, hardFork, doubleSpend, proofOfWork, merkleRoot, blockReward, difficultyRetarget, taprootStrike, segwitSlash, timestampAttack, dustAttack, opReturnBeam, orphanBlock, miningRig, satPerByte, fullNodeGuard, multisigStrike, nonceGrind, whitePaperRoll, asicHammer, rugPullYank, stackSatsBlast. Added to token_economy, hack_battle, math_blitz, speed_blitz themed pools. Co-Authored-By: Claude Opus 4.6 --- frontend/src/game/FightScene.ts | 552 +++++++++++++++++++++++++++++++- 1 file changed, 548 insertions(+), 4 deletions(-) diff --git a/frontend/src/game/FightScene.ts b/frontend/src/game/FightScene.ts index 63b895b..e47d6f4 100644 --- a/frontend/src/game/FightScene.ts +++ b/frontend/src/game/FightScene.ts @@ -105,14 +105,14 @@ const spriteAnims = { // Challenge type -> themed choreography (shown ~60% of the time) + generic fallbacks const CHALLENGE_THEMED: Record = { - speed_blitz: { themed: ['afterimageDash', 'lightningRush', 'rapidFlurry', 'tornadoSpin', 'corkscrewDive', 'warpDrive', 'backflipKick', 'bulletTimeAttack'], generic: ['dashPunch', 'multiHit', 'fullScreenDash', 'dashThrough', 'zoomRush', 'katanaCombo', 'cycloneKick', 'helicopterArms', 'breakdanceSweep'] }, + speed_blitz: { themed: ['afterimageDash', 'lightningRush', 'rapidFlurry', 'tornadoSpin', 'corkscrewDive', 'warpDrive', 'backflipKick', 'bulletTimeAttack', 'lightningBolt', 'channelForceClose', 'segwitSlash', 'doubleSpend'], generic: ['dashPunch', 'multiHit', 'fullScreenDash', 'dashThrough', 'zoomRush', 'katanaCombo', 'cycloneKick', 'helicopterArms', 'breakdanceSweep'] }, riddle: { themed: ['riddleBarrage', 'portalPunch', 'cloneStrike', 'mindBlast', 'hexCurse'], generic: ['teleportStrike', 'projectile', 'zoomRush', 'whipCrack', 'bombThrow', 'captchaTrap', 'glitchBeam'] }, code_golf: { themed: ['golfClubSmash', 'golfCartDrive', 'stackOverflow', 'segfault', 'blueScreen'], generic: ['projectile', 'flyingKick', 'dashPunch', 'rapidFlurry', 'hammerSmash', 'bodySlam', 'homeRunSwing'] }, roast_battle: { themed: ['fireBreath', 'dragonBreath', 'fireball', 'lavaSplash', 'heatVision', 'volcanoErupt'], generic: ['gunBurst', 'jetpackDive', 'multiHit', 'zoomRush', 'laserBeam', 'chainsawRev', 'rocketLauncher', 'dynamiteBlast'] }, hallucination_check: { themed: ['cloneStrike', 'portalPunch', 'shadowClone', 'glitchBeam', 'voidRift'], generic: ['teleportStrike', 'dashThrough', 'riddleBarrage', 'rapidFlurry', 'sniperShot', 'darkVoid', 'memeBeam'] }, - token_economy: { themed: ['coinShower', 'bitcoinCrash', 'nftRugPull', 'vendingDrop', 'popupSpam'], generic: ['dashPunch', 'flyingKick', 'uppercut', 'zoomRush', 'bombThrow', 'minigunSpray', 'pocketCannon'] }, + token_economy: { themed: ['coinShower', 'bitcoinCrash', 'nftRugPull', 'vendingDrop', 'popupSpam', 'blockReward', 'utxoScatter', 'feeSpike', 'satPerByte', 'stackSatsBlast', 'rugPullYank', 'dustAttack', 'multisigStrike'], generic: ['dashPunch', 'flyingKick', 'uppercut', 'zoomRush', 'bombThrow', 'minigunSpray', 'pocketCannon'] }, creative_writing: { themed: ['penStab', 'scrollBlast', 'bookStorm', 'enchantedArrow', 'newspaperRoll'], generic: ['jetpackDive', 'aerialSlam', 'projectile', 'rapidFlurry', 'katanaCombo', 'arcaneBarrage', 'memeBeam'] }, - math_blitz: { themed: ['mathAttack', 'dataStream', 'pixelBeam', 'hackerAttack', 'chainLightning'], generic: ['multiHit', 'dashPunch', 'fullScreenDash', 'rapidFlurry', 'laserBeam', 'hammerSmash', 'pinballCombo'] }, + math_blitz: { themed: ['mathAttack', 'dataStream', 'pixelBeam', 'hackerAttack', 'chainLightning', 'merkleRoot', 'difficultyRetarget', 'halvingStrike'], generic: ['multiHit', 'dashPunch', 'fullScreenDash', 'rapidFlurry', 'laserBeam', 'hammerSmash', 'pinballCombo'] }, trap_card: { themed: ['trapCardAttack', 'captchaTrap', 'spiderWeb', 'flashBang', 'smokeBomb'], generic: ['gunBurst', 'teleportStrike', 'groundPound', 'zoomRush', 'sniperShot', 'rocketLauncher', 'minigunSpray'] }, food_fight: { themed: ['pizzaSlam', 'bananaFling', 'pieSmash', 'hotdogWhip', 'watermelonBomb', 'sushiBarrage', 'burgerToss', 'tacoStorm', 'eggBombard', 'tomatoBarrage'], generic: ['popcornBlast', 'iceCreamFling', 'donutBarrage', 'cookieFling', 'nachoVolley', 'meatballStorm', 'candyBarrage'] }, wrestling_match: { themed: ['piledriver', 'powerbomb', 'chokeslam', 'tombstone', 'rko', 'clothesline', 'suplex', 'germanSuplex'], generic: ['bodySlam', 'grappleFlurry', 'hurricanrana', 'moonsault', 'elbowDrop', 'spear', 'stunner'] }, @@ -121,7 +121,7 @@ const CHALLENGE_THEMED: Record sports_showdown: { themed: ['homeRunSwing', 'slapShot', 'servingAce', 'fieldGoalKick', 'bodyCheck', 'soccerKick', 'basketballDunk'], generic: ['baseballBat', 'tennisRacket', 'hockeyStick', 'bowlingBallRoll', 'tennisBallVolley', 'dropKick'] }, nature_clash: { themed: ['treeSmash', 'icebergDrop', 'lavaSplash', 'tornadoFling', 'tsunamiWave', 'avalanche', 'earthquakeStrike'], generic: ['vineWhip', 'sandstorm', 'thornBarrage', 'pollenCloud', 'windSlash', 'meteorStrike'] }, space_war: { themed: ['blackHole', 'photonTorpedo', 'tractor_beam', 'warpDrive', 'alienAbduction', 'ionCannon', 'cosmicRay', 'bulletTimeAttack'], generic: ['laserSword', 'plasmaSword', 'asteroidBelt', 'satelliteLaser', 'plasmaBeam', 'solarFlare'] }, - hack_battle: { themed: ['hackerAttack', 'bugSwarm', 'stackOverflow', 'segfault', 'blueScreen', 'malwareInject', 'bulletTimeAttack'], generic: ['fourOhFour', 'ctrlAltDelete', 'aiUprising', 'captchaTrap', 'popupSpam', 'dataStream', 'glitchBeam'] }, + hack_battle: { themed: ['hackerAttack', 'bugSwarm', 'stackOverflow', 'segfault', 'blueScreen', 'malwareInject', 'bulletTimeAttack', 'hashSmash', 'miningRig', 'nodeSync', 'nonceGrind', 'proofOfWork'], generic: ['fourOhFour', 'ctrlAltDelete', 'aiUprising', 'captchaTrap', 'popupSpam', 'dataStream', 'glitchBeam'] }, meme_war: { themed: ['selfieStrike', 'dabAttack', 'flossAttack', 'yeetThrow', 'tPoseAssert', 'emojiBarrage', 'memeBeam'], generic: ['rubberChicken', 'fingerGuns', 'micDrop', 'ratioAttack', 'capThrow', 'touchGrass', 'noScope'] }, animal_kingdom: { themed: ['sharkBite', 'bearSwipe', 'eagleDive', 'bullCharge', 'gorillaSlam', 'wolfPack', 'batSwarm'], generic: ['snakeLunge', 'scorpionSting', 'crabPinch', 'spiderWeb', 'beeSwarm', 'catScratch', 'dogPile', 'dolphinFlip'] }, demolition: { themed: ['dynamiteBlast', 'c4Detonation', 'nukeStrike', 'grenadeBlast', 'rocketLauncher', 'volcanoErupt'], generic: ['fireworksBurst', 'partyPopper', 'pinataSmash', 'cherryBomb', 'confettiCannon', 'anvilDrop', 'pianoDrop'] }, @@ -6640,6 +6640,542 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) } + // --- BITCOIN-THEMED CHOREOGRAPHIES (30) --- + + async function lightningBolt(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial(); await k.wait(0.12) + const segments = isCritical ? 8 : 5 + let px = atk.pos.x + dir * 30, py = atk.pos.y - 40 + const bolts: any[] = [] + for (let i = 0; i < segments; i++) { + const nx = px + dir * 30 + (Math.random() - 0.5) * 20 + const ny = py + (Math.random() - 0.5) * 30 + const bolt = k.add([k.rect(3, 25), k.pos(px, py), k.color(safeColor(k, '#ffdd00')), k.opacity(1), k.z(18), k.rotate(Math.atan2(ny - py, nx - px) * 180 / Math.PI)]) + bolts.push(bolt) + sfxBulletHit(); px = nx; py = ny; await k.wait(0.02) + } + def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 14 : 6) + spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 14 : 6, '#ffdd00') + if (isCritical) { screenFlash('#ffff00'); sfxCritical() } + await k.wait(0.2) + bolts.forEach(b => b.destroy()) + const push = dir * (isCritical ? 80 : 35) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function hashSmash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial(); await k.wait(0.1) + const count = isCritical ? 16 : 8 + for (let i = 0; i < count; i++) { + const hx = def.pos.x + (Math.random() - 0.5) * 70 + const hash = k.add([k.text('#', { size: 14 }), k.pos(hx, -10), k.color(safeColor(k, '#00ff88')), k.opacity(0.9), k.z(18)]) + const ty = def.pos.y - 10 - Math.random() * 40 + k.tween(hash.pos.y, ty, 0.12 + Math.random() * 0.08, (v: number) => { hash.pos.y = v }, k.easings.easeInQuad).then(() => { hash.destroy(); spawnSparks(hx, ty, 2, '#00ff88'); sfxBulletHit() }) + await k.wait(0.025) + } + await k.wait(0.1); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 12 : 5) + if (isCritical) { screenFlash('#00ff88'); sfxExplosion() } + const push = dir * (isCritical ? 85 : 38) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + const blockDrop = makeDrop(28, 28, '#f7931a', '#ffd700', false) + + async function mempoolFlood(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial(); await k.wait(0.1) + const txCount = isCritical ? 24 : 12 + for (let i = 0; i < txCount; i++) { + const tx = k.add([k.rect(6, 3), k.pos(atk.pos.x + dir * 20, atk.pos.y - 30 + (Math.random() - 0.5) * 40), k.color(safeColor(k, Math.random() > 0.5 ? '#8844ff' : '#aa66ff')), k.opacity(0.8), k.z(16)]) + k.tween(tx.pos.x, def.pos.x + (Math.random() - 0.5) * 30, 0.2, (v: number) => { tx.pos.x = v }).then(() => { tx.destroy(); spawnSparks(tx.pos.x, tx.pos.y, 1, '#aa66ff') }) + k.tween(tx.pos.y, def.pos.y - 20 + (Math.random() - 0.5) * 30, 0.2, (v: number) => { tx.pos.y = v }) + if (i % 4 === 0) sfxBulletHit() + await k.wait(0.015) + } + await k.wait(0.15); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 12 : 5) + if (isCritical) { screenFlash('#8844ff'); sfxCritical() } + const push = dir * (isCritical ? 80 : 35) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function difficultyBomb(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial(); await k.wait(0.1) + const bomb = k.add([k.circle(8), k.pos(def.pos.x, def.pos.y - 30), k.color(safeColor(k, '#ff4400')), k.opacity(0.9), k.z(18)]) + await k.tween(8, isCritical ? 50 : 30, 0.3, (v: number) => { bomb.radius = v }, k.easings.easeOutQuad) + sfxExplosion(); k.shake(isCritical ? 18 : 10); screenFlash(isCritical ? '#ffffff' : '#ff4400', 0.12) + spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 18 : 10, '#ff4400') + spawnShockwave(def.pos.x, def.pos.y - 20, '#ff6600') + bomb.destroy(); def.play(isCritical ? 'knockback' : 'hit') + const push = dir * (isCritical ? 100 : 45) + k.tween(def.pos.x, origDX + push, 0.25, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.35); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function feeSpike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('attack'); sfxPunch(); await k.wait(0.08) + const spike = k.add([k.rect(4, 40), k.pos(def.pos.x, GROUND_Y), k.color(safeColor(k, '#ff2222')), k.opacity(0.9), k.z(16)]) + sfxSlideDown() + await k.tween(GROUND_Y, def.pos.y - 60, 0.12, (v: number) => { spike.pos.y = v }, k.easings.easeOutQuad) + sfxBonk(); k.shake(isCritical ? 14 : 7) + spawnSparks(def.pos.x, def.pos.y - 40, isCritical ? 10 : 5, '#ff2222') + def.play(isCritical ? 'knockback' : 'hit') + if (isCritical) { screenFlash('#ff0000'); sfxCritical() } + await k.wait(0.15) + k.tween(spike.opacity, 0, 0.2, (v: number) => { spike.opacity = v }).then(() => spike.destroy()) + const push = dir * (isCritical ? 75 : 30) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function utxoScatter(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial(); await k.wait(0.1) + const count = isCritical ? 14 : 7 + for (let i = 0; i < count; i++) { + const angle = (i / count) * Math.PI - Math.PI / 2 + const coin = k.add([k.circle(4), k.pos(atk.pos.x + dir * 15, atk.pos.y - 30), k.color(safeColor(k, '#ffd700')), k.opacity(1), k.z(17)]) + const tx = def.pos.x + Math.cos(angle) * 40 + (Math.random() - 0.5) * 20 + const ty = def.pos.y - 30 + Math.sin(angle) * 30 + k.tween(coin.pos.x, tx, 0.18, (v: number) => { coin.pos.x = v }, k.easings.easeOutQuad) + k.tween(coin.pos.y, ty, 0.18, (v: number) => { coin.pos.y = v }, k.easings.easeOutQuad).then(() => { coin.destroy(); spawnSparks(tx, ty, 2, '#ffd700'); sfxBulletHit() }) + await k.wait(0.02) + } + await k.wait(0.12); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 12 : 5) + if (isCritical) { screenFlash('#ffd700'); sfxCritical() } + const push = dir * (isCritical ? 80 : 35) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + const nodeSync = makeBeam('#00ff88', 5, 2) + + async function relayBounce(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('attack'); sfxPunch(); await k.wait(0.08) + const ball = k.add([k.circle(6), k.pos(atk.pos.x + dir * 20, atk.pos.y - 30), k.color(safeColor(k, '#8844ff')), k.opacity(1), k.z(18)]) + // Bounce off top + await k.tween(ball.pos.y, -10, 0.1, (v: number) => { ball.pos.y = v }, k.easings.easeOutQuad) + k.tween(ball.pos.x, (atk.pos.x + def.pos.x) / 2, 0.1, (v: number) => { ball.pos.x = v }) + sfxBoing() + // Bounce down to defender + await k.tween(ball.pos.y, def.pos.y - 25, 0.12, (v: number) => { ball.pos.y = v }, k.easings.easeInQuad) + k.tween(ball.pos.x, def.pos.x, 0.12, (v: number) => { ball.pos.x = v }) + sfxBonk(); k.shake(isCritical ? 14 : 6) + spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 12 : 5, '#8844ff') + ball.destroy(); def.play(isCritical ? 'knockback' : 'hit') + if (isCritical) { screenFlash('#8844ff'); sfxCritical() } + const push = dir * (isCritical ? 85 : 38) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function channelForceClose(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial() + // Draw lightning chain between fighters + const chain = k.add([k.rect(Math.abs(def.pos.x - atk.pos.x), 3), k.pos(Math.min(atk.pos.x, def.pos.x), atk.pos.y - 35), k.color(safeColor(k, '#ffdd00')), k.opacity(0.8), k.z(17)]) + await k.wait(0.2); sfxSpecial() + // Snap! Chain breaks + chain.destroy(); sfxExplosion() + spawnSparks((atk.pos.x + def.pos.x) / 2, atk.pos.y - 35, isCritical ? 16 : 8, '#ffdd00') + k.shake(isCritical ? 16 : 8); screenFlash('#ffdd00', 0.1) + def.play(isCritical ? 'knockback' : 'hit') + if (isCritical) sfxCritical() + const push = dir * (isCritical ? 95 : 42) + k.tween(def.pos.x, origDX + push, 0.25, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.35); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function halvingStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + const contactX = origDX - dir * 45 + sfxZoomWhoosh() + await k.tween(atk.pos.x, contactX, 0.1, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad) + // First hit + atk.play('attack'); sfxPunch(); def.play('hit'); k.shake(4) + spawnSparks(def.pos.x, def.pos.y - 25, 4, '#f7931a') + await k.wait(0.12) + // Second hit (the halving) + atk.play('kick'); sfxPunch(); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 12 : 6) + spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 10 : 5, '#ffd700') + if (isCritical) { screenFlash('#f7931a'); sfxCritical() } + const push = dir * (isCritical ? 85 : 38) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3) + await Promise.all([ + k.tween(atk.pos.x, origAX, 0.2, (v: number) => { atk.pos.x = v }, k.easings.easeInQuad), + k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad), + ]) + } + + async function coldStorage(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial(); await k.wait(0.1) + // Freeze aura around defender + const ice = k.add([k.circle(isCritical ? 40 : 25), k.pos(def.pos.x, def.pos.y - 25), k.color(safeColor(k, '#88ddff')), k.opacity(0.4), k.z(15)]) + spawnSparks(def.pos.x, def.pos.y - 30, 8, '#aaeeff') + sfxBoing(); await k.wait(0.25) + // Shatter + sfxExplosion(); ice.destroy() + spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 16 : 8, '#88ddff') + k.shake(isCritical ? 14 : 7); def.play(isCritical ? 'knockback' : 'hit') + if (isCritical) { screenFlash('#88ddff'); sfxCritical() } + const push = dir * (isCritical ? 80 : 35) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + const paperWalletSlash = makeSwing(30, 2, '#f0e8d0', '#ffd700', 8, 8) + + async function hardFork(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial(); await k.wait(0.1) + // Two beams split from one + const startX = atk.pos.x + dir * 25 + const beamA = k.add([k.rect(3, 60), k.pos(startX, atk.pos.y - 50), k.color(safeColor(k, '#ff4400')), k.opacity(0.8), k.z(17), k.rotate(15 * dir)]) + const beamB = k.add([k.rect(3, 60), k.pos(startX, atk.pos.y - 30), k.color(safeColor(k, '#4488ff')), k.opacity(0.8), k.z(17), k.rotate(-15 * dir)]) + sfxSpecial() + await Promise.all([ + k.tween(beamA.pos.x, def.pos.x, 0.15, (v: number) => { beamA.pos.x = v }), + k.tween(beamB.pos.x, def.pos.x, 0.15, (v: number) => { beamB.pos.x = v }), + ]) + beamA.destroy(); beamB.destroy() + sfxExplosion(); k.shake(isCritical ? 16 : 8) + spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 14 : 7, '#ff4400') + spawnSparks(def.pos.x, def.pos.y - 20, isCritical ? 14 : 7, '#4488ff') + def.play(isCritical ? 'knockback' : 'hit') + if (isCritical) { screenFlash('#ffffff'); sfxCritical() } + const push = dir * (isCritical ? 90 : 40) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function doubleSpend(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + const contactX = origDX - dir * 45 + sfxZoomWhoosh() + await k.tween(atk.pos.x, contactX, 0.1, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad) + // Two rapid identical hits + for (let i = 0; i < 2; i++) { + atk.play(i === 0 ? 'attack' : 'kick'); sfxPunch() + def.play('hit'); k.shake(5 + i * 3) + spawnSparks(def.pos.x, def.pos.y - 25, 5, '#ff2222') + spawnAfterimages(atk, 1) + await k.wait(0.08) + } + def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 14 : 7) + if (isCritical) { screenFlash('#ff2222'); sfxCritical() } + const push = dir * (isCritical ? 90 : 40) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3) + await Promise.all([ + k.tween(atk.pos.x, origAX, 0.2, (v: number) => { atk.pos.x = v }, k.easings.easeInQuad), + k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad), + ]) + } + + const proofOfWork = makeSwing(12, 30, '#888888', '#f7931a', 14, 14) + + async function merkleRoot(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial(); await k.wait(0.1) + // Roots grow up from ground under defender + const roots: any[] = [] + for (let i = 0; i < (isCritical ? 6 : 3); i++) { + const rx = def.pos.x + (i - (isCritical ? 2.5 : 1)) * 12 + const root = k.add([k.rect(3, 0), k.pos(rx, GROUND_Y), k.color(safeColor(k, '#44aa22')), k.opacity(0.8), k.z(15)]) + roots.push(root) + k.tween(0, 60 + Math.random() * 20, 0.15, (v: number) => { root.height = v; root.pos.y = GROUND_Y - v }) + await k.wait(0.03) + } + sfxBonk(); k.shake(isCritical ? 14 : 7) + spawnSparks(def.pos.x, def.pos.y - 20, isCritical ? 12 : 6, '#44aa22') + def.play(isCritical ? 'knockback' : 'hit') + if (isCritical) { screenFlash('#44aa22'); sfxCritical() } + await k.wait(0.2) + roots.forEach(r => r.destroy()) + const push = dir * (isCritical ? 85 : 38) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function blockReward(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxBoing() + // Gold coins gather around attacker then blast at defender + const coins: any[] = [] + for (let i = 0; i < (isCritical ? 10 : 5); i++) { + const c = k.add([k.circle(4), k.pos(atk.pos.x + (Math.random() - 0.5) * 40, atk.pos.y - 50 + (Math.random() - 0.5) * 30), k.color(safeColor(k, '#ffd700')), k.opacity(1), k.z(17)]) + coins.push(c) + } + await k.wait(0.2); sfxZoomWhoosh() + // All coins fly at defender + for (const c of coins) { + k.tween(c.pos.x, def.pos.x + (Math.random() - 0.5) * 20, 0.12, (v: number) => { c.pos.x = v }) + k.tween(c.pos.y, def.pos.y - 25 + (Math.random() - 0.5) * 20, 0.12, (v: number) => { c.pos.y = v }).then(() => { c.destroy(); sfxBulletHit() }) + } + await k.wait(0.15); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 14 : 7) + spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 14 : 7, '#ffd700') + if (isCritical) { screenFlash('#ffd700'); sfxCritical() } + const push = dir * (isCritical ? 85 : 38) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function difficultyRetarget(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial() + // Screen warps then snaps + const overlay = k.add([k.rect(k.width(), k.height()), k.pos(0, 0), k.color(safeColor(k, '#ff00ff')), k.opacity(0), k.z(25)]) + await k.tween(0, 0.3, 0.2, (v: number) => { overlay.opacity = v }) + k.shake(isCritical ? 18 : 9) + await k.tween(0.3, 0, 0.15, (v: number) => { overlay.opacity = v }) + overlay.destroy(); sfxExplosion() + spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 14 : 7, '#ff00ff') + def.play(isCritical ? 'knockback' : 'hit') + if (isCritical) { screenFlash('#ff00ff'); sfxCritical() } + const push = dir * (isCritical ? 85 : 38) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function taprootStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial(); await k.wait(0.1) + // Single root shoots up from below defender + const root = k.add([k.rect(5, 0), k.pos(def.pos.x, GROUND_Y), k.color(safeColor(k, '#22cc44')), k.opacity(0.9), k.z(16)]) + sfxSlideDown() + await k.tween(0, 80, 0.15, (v: number) => { root.height = v; root.pos.y = GROUND_Y - v }, k.easings.easeOutQuad) + sfxBonk(); k.shake(isCritical ? 14 : 7) + spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 12 : 6, '#22cc44') + def.play(isCritical ? 'knockback' : 'hit') + if (isCritical) { screenFlash('#22cc44'); sfxCritical() } + await k.wait(0.15) + k.tween(root.opacity, 0, 0.2, (v: number) => { root.opacity = v }).then(() => root.destroy()) + const push = dir * (isCritical ? 80 : 35) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function segwitSlash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + const contactX = origDX - dir * 40 + sfxZoomWhoosh() + await k.tween(atk.pos.x, contactX, 0.08, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad) + atk.play('attack'); sfxPunch() + // Compressed slash visual — thin line + const slash = k.add([k.rect(60, 2), k.pos(def.pos.x - 30, def.pos.y - 30), k.color(safeColor(k, '#44aaff')), k.opacity(0.9), k.z(18)]) + await k.wait(0.06); slash.destroy() + def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 12 : 5) + spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 10 : 5, '#44aaff') + if (isCritical) { screenFlash('#44aaff'); sfxCritical() } + const push = dir * (isCritical ? 80 : 35) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3) + await Promise.all([ + k.tween(atk.pos.x, origAX, 0.2, (v: number) => { atk.pos.x = v }, k.easings.easeInQuad), + k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad), + ]) + } + + async function timestampAttack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial(); await k.wait(0.1) + const clock = k.add([k.circle(12), k.pos(atk.pos.x + dir * 20, atk.pos.y - 35), k.color(safeColor(k, '#ffcc00')), k.opacity(0.8), k.z(18)]) + sfxZoomWhoosh() + await k.tween(clock.pos.x, def.pos.x, 0.15, (v: number) => { clock.pos.x = v }) + clock.destroy(); sfxBonk(); k.shake(isCritical ? 14 : 7) + spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 12 : 6, '#ffcc00') + def.play(isCritical ? 'knockback' : 'hit') + if (isCritical) { screenFlash('#ffcc00'); sfxCritical() } + const push = dir * (isCritical ? 85 : 38) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function dustAttack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial(); await k.wait(0.08) + const count = isCritical ? 30 : 15 + for (let i = 0; i < count; i++) { + const p = k.add([k.circle(1), k.pos(atk.pos.x + dir * 15, atk.pos.y - 30), k.color(safeColor(k, '#aaaaaa')), k.opacity(0.6), k.z(16)]) + const tx = def.pos.x + (Math.random() - 0.5) * 50 + const ty = def.pos.y - 30 + (Math.random() - 0.5) * 40 + k.tween(p.pos.x, tx, 0.15, (v: number) => { p.pos.x = v }) + k.tween(p.pos.y, ty, 0.15, (v: number) => { p.pos.y = v }).then(() => p.destroy()) + await k.wait(0.008) + } + await k.wait(0.1); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 10 : 4) + if (isCritical) { screenFlash('#aaaaaa'); sfxCritical() } + const push = dir * (isCritical ? 70 : 30) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + const opReturnBeam = makeBeam('#ff8800', 8, 1) + + async function orphanBlock(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial(); await k.wait(0.1) + const block = k.add([k.rect(20, 20), k.pos(def.pos.x + dir * 40, -20), k.color(safeColor(k, '#ff6600')), k.opacity(0.7), k.z(17)]) + sfxSlideDown() + // Drops, misses slightly, then rebounds into defender + await k.tween(block.pos.y, def.pos.y - 20, 0.15, (v: number) => { block.pos.y = v }, k.easings.easeInQuad) + sfxBoing() + await k.tween(block.pos.y, def.pos.y - 50, 0.08, (v: number) => { block.pos.y = v }, k.easings.easeOutQuad) + k.tween(block.pos.x, def.pos.x, 0.08, (v: number) => { block.pos.x = v }) + await k.tween(block.pos.y, def.pos.y - 25, 0.08, (v: number) => { block.pos.y = v }, k.easings.easeInQuad) + block.destroy(); sfxBonk(); k.shake(isCritical ? 14 : 7) + spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 12 : 6, '#ff6600') + def.play(isCritical ? 'knockback' : 'hit') + if (isCritical) { screenFlash('#ff6600'); sfxCritical() } + const push = dir * (isCritical ? 85 : 38) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function miningRig(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial(); await k.wait(0.1) + const count = isCritical ? 12 : 6 + for (let i = 0; i < count; i++) { + const hash = k.add([k.rect(4, 4), k.pos(atk.pos.x + dir * 20, atk.pos.y - 30 + (Math.random() - 0.5) * 20), k.color(safeColor(k, '#00ff88')), k.opacity(0.8), k.z(17)]) + k.tween(hash.pos.x, def.pos.x, 0.12, (v: number) => { hash.pos.x = v }).then(() => { hash.destroy(); sfxBulletHit(); spawnSparks(def.pos.x, hash.pos.y, 2, '#00ff88') }) + await k.wait(0.04) + } + await k.wait(0.12); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 12 : 5) + if (isCritical) { screenFlash('#00ff88'); sfxCritical() } + const push = dir * (isCritical ? 80 : 35) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function satPerByte(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial() + // Fee meter rises under defender + const meter = k.add([k.rect(6, 0), k.pos(def.pos.x, GROUND_Y), k.color(safeColor(k, '#ff2222')), k.opacity(0.8), k.z(15)]) + await k.tween(0, isCritical ? 90 : 60, 0.25, (v: number) => { meter.height = v; meter.pos.y = GROUND_Y - v }) + // Explodes + sfxExplosion(); meter.destroy(); k.shake(isCritical ? 16 : 8) + spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 14 : 7, '#ff2222') + spawnShockwave(def.pos.x, def.pos.y - 20, '#ff4400') + def.play(isCritical ? 'knockback' : 'hit') + if (isCritical) { screenFlash('#ff2222'); sfxCritical() } + const push = dir * (isCritical ? 90 : 40) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function fullNodeGuard(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + // Shield forms, then counter-attacks + atk.play('special'); sfxBoing() + const shield = k.add([k.circle(25), k.pos(atk.pos.x, atk.pos.y - 25), k.color(safeColor(k, '#00ff88')), k.opacity(0.3), k.z(16)]) + await k.wait(0.2) + shield.destroy(); sfxZoomWhoosh() + // Counter-dash + const contactX = origDX - dir * 45 + await k.tween(atk.pos.x, contactX, 0.1, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad) + atk.play('attack'); sfxPunch() + def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 14 : 7) + spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 12 : 6, '#00ff88') + if (isCritical) { screenFlash('#00ff88'); sfxCritical() } + const push = dir * (isCritical ? 90 : 40) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3) + await Promise.all([ + k.tween(atk.pos.x, origAX, 0.2, (v: number) => { atk.pos.x = v }, k.easings.easeInQuad), + k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad), + ]) + } + + async function multisigStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + // 3 simultaneous hits from different angles + atk.play('special'); sfxSpecial(); await k.wait(0.1) + const angles = [-40, 0, 40] + for (const offset of angles) { + const proj = k.add([k.circle(5), k.pos(atk.pos.x + dir * 15, atk.pos.y - 30 + offset), k.color(safeColor(k, '#f7931a')), k.opacity(0.9), k.z(17)]) + k.tween(proj.pos.x, def.pos.x, 0.12, (v: number) => { proj.pos.x = v }) + k.tween(proj.pos.y, def.pos.y - 25, 0.12, (v: number) => { proj.pos.y = v }).then(() => { proj.destroy(); sfxBulletHit() }) + } + await k.wait(0.15); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 14 : 7) + spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 14 : 7, '#f7931a') + if (isCritical) { screenFlash('#f7931a'); sfxCritical() } + const push = dir * (isCritical ? 85 : 38) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + async function nonceGrind(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + const contactX = origDX - dir * 45 + sfxZoomWhoosh() + await k.tween(atk.pos.x, contactX, 0.1, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad) + // Rapid grinding hits + const hits = isCritical ? 6 : 3 + for (let i = 0; i < hits; i++) { + atk.play(i % 2 === 0 ? 'attack' : 'kick'); sfxPunch() + def.play('hit'); k.shake(3 + i) + spawnSparks(def.pos.x + (Math.random() - 0.5) * 15, def.pos.y - 25, 3, '#ffaa00') + await k.wait(0.04) + } + def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 14 : 7) + if (isCritical) { screenFlash('#ffaa00'); sfxCritical() } + const push = dir * (isCritical ? 90 : 40) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3) + await Promise.all([ + k.tween(atk.pos.x, origAX, 0.2, (v: number) => { atk.pos.x = v }, k.easings.easeInQuad), + k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad), + ]) + } + + async function whitePaperRoll(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('attack'); sfxPunch(); await k.wait(0.08) + // Paper scroll flies at defender + const paper = k.add([k.rect(20, 8), k.pos(atk.pos.x + dir * 20, atk.pos.y - 30), k.color(safeColor(k, '#f0e8d0')), k.opacity(0.9), k.z(17), k.rotate(0)]) + sfxZoomWhoosh() + k.tween(paper.angle, 720, 0.18, (v: number) => { paper.angle = v }) + await k.tween(paper.pos.x, def.pos.x, 0.18, (v: number) => { paper.pos.x = v }) + paper.destroy(); sfxBonk(); k.shake(isCritical ? 12 : 5) + spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 10 : 5, '#f0e8d0') + def.play(isCritical ? 'knockback' : 'hit') + if (isCritical) { screenFlash('#f0e8d0'); sfxCritical() } + const push = dir * (isCritical ? 80 : 35) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + const asicHammer = makeSwing(10, 25, '#666666', '#f7931a', 12, 12) + + async function rugPullYank(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { + atk.play('special'); sfxSpecial() + // Ground slides away under defender + const rug = k.add([k.rect(80, 4), k.pos(def.pos.x - 40, GROUND_Y - 2), k.color(safeColor(k, '#ff4488')), k.opacity(0.6), k.z(4)]) + await k.wait(0.1) + sfxZoomWhoosh() + await k.tween(rug.pos.x, rug.pos.x + dir * 100, 0.15, (v: number) => { rug.pos.x = v }) + rug.destroy() + // Defender falls + await k.tween(def.pos.y, def.pos.y + 15, 0.08, (v: number) => { def.pos.y = v }, k.easings.easeInQuad) + sfxBonk(); k.shake(isCritical ? 14 : 7) + await k.tween(def.pos.y, GROUND_Y, 0.08, (v: number) => { def.pos.y = v }) + def.play(isCritical ? 'knockback' : 'hit') + spawnSparks(def.pos.x, GROUND_Y, isCritical ? 10 : 5, '#ff4488') + if (isCritical) { screenFlash('#ff4488'); sfxCritical() } + const push = dir * (isCritical ? 85 : 38) + k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad) + await k.wait(0.3); atk.play('idle') + await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) + } + + const stackSatsBlast = makeThrow('#ffd700', 5, 8, 50, true) + const choreographyMap: Record = { dashPunch, aerialSlam, flyingKick, dashThrough, uppercut, multiHit, projectile, jetpackDive, gunBurst, groundPound, @@ -6743,6 +7279,14 @@ export async function createFightScene(config: FightSceneConfig) { // Tier 5+ ultimateArmageddon, ultimateOmegaBeam, ultimateBeyondTheScreen, ultimateMatrixDodge, ultimateWorldEnder, ultimateAbsoluteZero, ultimateSolarCannon, ultimatePhantomStrike, ultimateGodPunch, ultimateVoidCollapse, + // --- BITCOIN-THEMED (30 new) --- + lightningBolt, hashSmash, blockDrop, mempoolFlood, difficultyBomb, + feeSpike, utxoScatter, nodeSync, relayBounce, channelForceClose, + halvingStrike, coldStorage, paperWalletSlash, hardFork, doubleSpend, + proofOfWork, merkleRoot, blockReward, difficultyRetarget, taprootStrike, + segwitSlash, timestampAttack, dustAttack, opReturnBeam, orphanBlock, + miningRig, satPerByte, fullNodeGuard, multisigStrike, nonceGrind, + whitePaperRoll, asicHammer, rugPullYank, stackSatsBlast, // --- THE CREATOR: Bitcoin-themed moves --- bitcoinRain, lightningInvoice, satoshiStrike, hashRateOverload, blockchainSlam, creatorMode, morphStrike, divineIntervention,