From 1ca8a6feb01895b95b161314ef9c6e0f0c8cacd4 Mon Sep 17 00:00:00 2001 From: Dorian Date: Sun, 8 Mar 2026 20:30:02 +0000 Subject: [PATCH] refactor: type Fighter interface for all choreographies Define Fighter type alias using Kaplay's GameObj with SpriteComp, PosComp, ScaleComp, AnchorComp, OpacityComp, ColorComp, RotateComp, and ZComp. Replace 81 choreography function signatures from (atk: any, def: any, ...) to (atk: Fighter, def: Fighter, ...). Type loadSpriteWithTimeout opts as LoadSpriteOpt. Type lines/bands arrays as GameObj[]. Type spawnAfterimages and cameraZoom fighter params. Add as Fighter casts at k.get() call sites. Co-Authored-By: Claude Opus 4.6 --- frontend/src/game/FightScene.ts | 271 ++++++++++++++++---------------- 1 file changed, 137 insertions(+), 134 deletions(-) diff --git a/frontend/src/game/FightScene.ts b/frontend/src/game/FightScene.ts index dabe35b..ddb8b39 100644 --- a/frontend/src/game/FightScene.ts +++ b/frontend/src/game/FightScene.ts @@ -1,4 +1,7 @@ import kaplay from 'kaplay' +import type { GameObj, SpriteComp, PosComp, ScaleComp, AnchorComp, OpacityComp, ColorComp, RotateComp, ZComp, LoadSpriteOpt } from 'kaplay' + +type Fighter = GameObj import { generateSpriteSheet, generateJudgeSpriteSheet, generateHumanSpriteSheet, generateHumanFighterSpriteSheet, getBotColors, FRAME_SIZE, MAX_FRAMES, TOTAL_ROWS, ANIMATIONS, JUDGE_ANIMATIONS, JUDGE_MAX_FRAMES, JUDGE_ROWS, HUMAN_ANIMATIONS, HUMAN_MAX_FRAMES, HUMAN_ROWS, type SpriteCustomization } from './sprites' import { sfxPunch, sfxKick, sfxSpecial, sfxCritical, sfxGunshot, sfxBulletHit, @@ -293,7 +296,7 @@ export async function createFightScene(config: FightSceneConfig) { const isHumanB = botB.archetype === 'human' // Helper: load sprite with a 5s timeout so mobile never hangs forever - async function loadSpriteWithTimeout(name: string, src: string, opts: any): Promise { + async function loadSpriteWithTimeout(name: string, src: string, opts: LoadSpriteOpt): Promise { try { await Promise.race([ k.loadSprite(name, src, opts), @@ -519,7 +522,7 @@ export async function createFightScene(config: FightSceneConfig) { // Scanline glitch — horizontal bands flicker function scanlineGlitch(duration: number = 0.3) { - const lines: any[] = [] + const lines: GameObj[] = [] const count = 6 + Math.floor(Math.random() * 8) for (let i = 0; i < count; i++) { const ly = Math.random() * H @@ -540,7 +543,7 @@ export async function createFightScene(config: FightSceneConfig) { // VHS tracking distortion — horizontal offset bands function vhsTracking(duration: number = 0.4) { - const bands: any[] = [] + const bands: GameObj[] = [] const bandCount = 3 for (let i = 0; i < bandCount; i++) { const by = Math.random() * H @@ -576,8 +579,8 @@ export async function createFightScene(config: FightSceneConfig) { // Schizo cut — rapid zoom/position jitter simulating jump cuts async function schizoCut() { - const fA = k.get('fighterA')[0] - const fB = k.get('fighterB')[0] + const fA = k.get('fighterA')[0] as Fighter + const fB = k.get('fighterB')[0] as Fighter if (!fA || !fB) return const origAX = fA.pos.x, origBX = fB.pos.x const origAY = fA.pos.y, origBY = fB.pos.y @@ -598,7 +601,7 @@ export async function createFightScene(config: FightSceneConfig) { // Hyperspeed lines — converging toward a point function hyperSpeedLines(targetX: number, targetY: number, duration: number = 0.3) { - const lines: any[] = [] + const lines: GameObj[] = [] for (let i = 0; i < 16; i++) { const angle = (i / 16) * Math.PI * 2 const dist = 300 + Math.random() * 200 @@ -650,7 +653,7 @@ export async function createFightScene(config: FightSceneConfig) { } // Spawn afterimages: trailing ghost sprites behind a moving fighter - function spawnAfterimages(fighter: any, count: number = 3) { + function spawnAfterimages(fighter: Fighter, count: number = 3) { if (!fighter?.exists()) return for (let i = 0; i < count; i++) { const ghost = k.add([ @@ -667,7 +670,7 @@ export async function createFightScene(config: FightSceneConfig) { // Camera zoom: scale fighters toward/away from a focal point for impact emphasis async function cameraZoom( - fighters: any[], + fighters: Fighter[], savedScales: { x: number; y: number }[], zoomFactor: number, duration: number, @@ -685,7 +688,7 @@ export async function createFightScene(config: FightSceneConfig) { } // Grotesque close-up overlays removed — noops retained for call-site compatibility - function spawnGrotesqueDetails(_fighter: any, _scaleFactor: number) {} + function spawnGrotesqueDetails(_fighter: Fighter, _scaleFactor: number) {} function destroyGrotesqueDetails() {} // Arena-specific background decoration function drawArenaDecor() { @@ -2038,7 +2041,7 @@ export async function createFightScene(config: FightSceneConfig) { // === CHOREOGRAPHY FUNCTIONS === // Each returns a Promise and handles all movement + animation - async function dashPunch(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function dashPunch(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const contactX = origDX - dir * 50 // Fast sprint to contact await k.tween(atk.pos.x, contactX, 0.12, (v) => { atk.pos.x = v }, k.easings.easeOutQuad) @@ -2061,7 +2064,7 @@ export async function createFightScene(config: FightSceneConfig) { ]) } - async function aerialSlam(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function aerialSlam(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const origAY = atk.pos.y const contactX = origDX - dir * 20 // Jump way up @@ -2090,7 +2093,7 @@ export async function createFightScene(config: FightSceneConfig) { ]) } - async function flyingKick(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function flyingKick(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const origAY = atk.pos.y const contactX = origDX - dir * 30 // Small jump + fly horizontally @@ -2117,7 +2120,7 @@ export async function createFightScene(config: FightSceneConfig) { ]) } - async function dashThrough(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function dashThrough(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const behindX = origDX + dir * 80 atk.play('attack') sfxSpecial() @@ -2137,7 +2140,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.wait(0.1) } - async function uppercut(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function uppercut(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const contactX = origDX - dir * 40 const origDY = def.pos.y // Run to contact @@ -2163,7 +2166,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(atk.pos.x, origAX, 0.2, (v) => { atk.pos.x = v }, k.easings.easeInQuad) } - async function multiHit(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function multiHit(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const contactX = origDX - dir * 45 // Sprint to contact await k.tween(atk.pos.x, contactX, 0.1, (v) => { atk.pos.x = v }, k.easings.easeOutQuad) @@ -2192,7 +2195,7 @@ export async function createFightScene(config: FightSceneConfig) { ]) } - async function projectile(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function projectile(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxSpecial() await k.wait(0.25) @@ -2213,7 +2216,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function jetpackDive(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function jetpackDive(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const origAY = atk.pos.y sfxJetpack() // Jetpack blast off - fly way up off screen @@ -2250,7 +2253,7 @@ export async function createFightScene(config: FightSceneConfig) { ]) } - async function gunBurst(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function gunBurst(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') // Spawn visible pixel gun prop const gunX = atk.pos.x + dir * 15 @@ -2292,7 +2295,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function groundPound(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function groundPound(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const origAY = atk.pos.y // Jump super high off screen sfxJetpack() @@ -2332,7 +2335,7 @@ export async function createFightScene(config: FightSceneConfig) { ]) } - async function teleportStrike(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function teleportStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Fade out sfxSpecial() spawnSparks(atk.pos.x, atk.pos.y - 30, 8, '#b83dff') @@ -2365,7 +2368,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function fullScreenDash(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function fullScreenDash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Back up to wall const wallX = dir === 1 ? 20 : W - 20 await k.tween(atk.pos.x, wallX, 0.15, (v) => { atk.pos.x = v }, k.easings.easeOutQuad) @@ -2448,7 +2451,7 @@ export async function createFightScene(config: FightSceneConfig) { } // CODE GOLF: swing a giant golf club, golf balls fly at opponent - async function golfClubSmash(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function golfClubSmash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const contactX = origDX - dir * 60 await k.tween(atk.pos.x, contactX, 0.15, (v) => { atk.pos.x = v }, k.easings.easeOutQuad) // Spawn golf club (long thin rect) @@ -2509,7 +2512,7 @@ export async function createFightScene(config: FightSceneConfig) { } // ROAST BATTLE: fire breath — stream of fire from attacker's face - async function fireBreath(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function fireBreath(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const contactX = origDX - dir * 80 await k.tween(atk.pos.x, contactX, 0.12, (v) => { atk.pos.x = v }, k.easings.easeOutQuad) atk.play('special') @@ -2567,7 +2570,7 @@ export async function createFightScene(config: FightSceneConfig) { } // RIDDLE: question mark barrage rains from the sky - async function riddleBarrage(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function riddleBarrage(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxSpecial() sfxZap() @@ -2612,7 +2615,7 @@ export async function createFightScene(config: FightSceneConfig) { } // HALLUCINATION CHECK: clone confusion — multiple fake attackers appear - async function cloneStrike(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function cloneStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { sfxZap() // Spawn 3-5 "ghost" clones that zip around const cloneCount = isCritical ? 5 : 3 @@ -2662,7 +2665,7 @@ export async function createFightScene(config: FightSceneConfig) { } // TOKEN ECONOMY: coin shower — coins rain down on opponent - async function coinShower(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function coinShower(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxSpecial() sfxBoing() @@ -2698,7 +2701,7 @@ export async function createFightScene(config: FightSceneConfig) { } // CREATIVE WRITING: giant pen stab + ink splatter - async function penStab(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function penStab(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Spawn a giant pen/quill const penX = atk.pos.x + dir * 15 const penY = atk.pos.y - 70 @@ -2757,7 +2760,7 @@ export async function createFightScene(config: FightSceneConfig) { } // MATH BLITZ: number projectiles fly at opponent - async function mathAttack(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function mathAttack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxSpecial() await k.wait(0.15) @@ -2798,7 +2801,7 @@ export async function createFightScene(config: FightSceneConfig) { } // TRAP CARD: card shuriken + trap springs - async function trapCardAttack(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function trapCardAttack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxSpecial() await k.wait(0.1) @@ -2863,7 +2866,7 @@ export async function createFightScene(config: FightSceneConfig) { } // SPEED BLITZ: afterimage dash — leaves ghost copies behind - async function afterimageDash(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function afterimageDash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { sfxSpecial() const contactX = origDX - dir * 40 // Leave afterimages as we dash @@ -2911,7 +2914,7 @@ export async function createFightScene(config: FightSceneConfig) { } // BULLET TIME: slow-mo dodge, afterimages trail behind, golden traces, devastating counter - async function bulletTimeAttack(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function bulletTimeAttack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Phase 1: Darken screen — entering bullet time const dim = k.add([k.rect(W, H), k.pos(0, 0), k.color(safeColor(k, '#000011')), k.opacity(0), k.z(25)]) await k.tween(0, 0.45, 0.15, (v) => { dim.opacity = v }) @@ -3017,7 +3020,7 @@ export async function createFightScene(config: FightSceneConfig) { } // INVISIBLE SPEED: so fast you can barely see — just blur lines and impact - async function lightningRush(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function lightningRush(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { sfxSpecial() // Attacker vanishes atk.opacity = 0 @@ -3069,7 +3072,7 @@ export async function createFightScene(config: FightSceneConfig) { } // ZOOM RUSH: fly toward camera (scale huge) then slam back into opponent - async function zoomRush(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function zoomRush(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const origAY = atk.pos.y const origScaleX = atk.scale.x const origScaleY = atk.scale.y @@ -3150,7 +3153,7 @@ export async function createFightScene(config: FightSceneConfig) { } // RAPID FLURRY: DBZ-style machine gun punches — so fast it's a blur - async function rapidFlurry(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function rapidFlurry(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const contactX = origDX - dir * 40 // Sprint to contact sfxSpecial() @@ -3210,7 +3213,7 @@ export async function createFightScene(config: FightSceneConfig) { } // SWORD SLASH: giant energy sword swing with arc trail - async function swordSlash(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function swordSlash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const contactX = origDX - dir * 50 await k.tween(atk.pos.x, contactX, 0.1, (v) => { atk.pos.x = v }, k.easings.easeOutQuad) atk.play('special') @@ -3245,7 +3248,7 @@ export async function createFightScene(config: FightSceneConfig) { } // HAMMER SMASH: pull out giant hammer, overhead slam with ground crack - async function hammerSmash(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function hammerSmash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const contactX = origDX - dir * 40 await k.tween(atk.pos.x, contactX, 0.12, (v) => { atk.pos.x = v }, k.easings.easeOutQuad) // Giant hammer @@ -3277,7 +3280,7 @@ export async function createFightScene(config: FightSceneConfig) { } // LASER BEAM: charge up then fire continuous beam across screen - async function laserBeam(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function laserBeam(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxSpecial() // Charge up (growing ball of energy) @@ -3308,7 +3311,7 @@ export async function createFightScene(config: FightSceneConfig) { } // ROCKET LAUNCHER: fire a big slow rocket that explodes on impact - async function rocketLauncher(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function rocketLauncher(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxSpecial() await k.wait(0.15) @@ -3352,7 +3355,7 @@ export async function createFightScene(config: FightSceneConfig) { } // BOMB THROW: lob an arcing bomb that bounces once then detonates - async function bombThrow(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function bombThrow(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('attack') sfxSpecial() const bombX = atk.pos.x + dir * 20 @@ -3387,7 +3390,7 @@ export async function createFightScene(config: FightSceneConfig) { } // MINIGUN SPRAY: sustained automatic fire with shell casings - async function minigunSpray(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function minigunSpray(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') // Spawn visible pixel minigun const gpx = atk.pos.x + dir * 15 @@ -3433,7 +3436,7 @@ export async function createFightScene(config: FightSceneConfig) { } // SNIPER SHOT: long pause, laser sight, then one devastating hit - async function sniperShot(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function sniperShot(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Back up to wall const wallX = dir === 1 ? 30 : W - 30 await k.tween(atk.pos.x, wallX, 0.2, (v) => { atk.pos.x = v }, k.easings.easeOutQuad) @@ -3471,7 +3474,7 @@ export async function createFightScene(config: FightSceneConfig) { } // WHIP CRACK: ranged whip attack with crack sound - async function whipCrack(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function whipCrack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('attack') sfxSpecial() await k.wait(0.1) @@ -3514,7 +3517,7 @@ export async function createFightScene(config: FightSceneConfig) { } // KATANA COMBO: triple slash with cross patterns - async function katanaCombo(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function katanaCombo(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const contactX = origDX - dir * 45 sfxSpecial() await k.tween(atk.pos.x, contactX, 0.08, (v) => { atk.pos.x = v }, k.easings.easeOutQuad) @@ -3552,7 +3555,7 @@ export async function createFightScene(config: FightSceneConfig) { } // CHAINSAW: charge with buzzing chainsaw, multi-hit grind - async function chainsawRev(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function chainsawRev(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const contactX = origDX - dir * 35 atk.play('special') // Chainsaw buzzing (visual: vibrating rect) @@ -3595,7 +3598,7 @@ export async function createFightScene(config: FightSceneConfig) { } // MOTORBIKE CHARGE: ride a motorbike across the screen and slam into defender - async function motorbikeCharge(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function motorbikeCharge(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Back up to edge const startX = dir === 1 ? -30 : W + 30 atk.opacity = 0 @@ -3643,7 +3646,7 @@ export async function createFightScene(config: FightSceneConfig) { } // CAR SMASH: drive a car from off-screen, defender gets sent flying - async function carSmash(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function carSmash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.opacity = 0 const carY = GROUND_Y - 20 const startX = dir === 1 ? -60 : W + 60 @@ -3688,7 +3691,7 @@ export async function createFightScene(config: FightSceneConfig) { } // BOAT CANNON: a boat slides across the ground and fires cannons - async function boatCannon(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function boatCannon(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxSpecial() // Build boat @@ -3731,7 +3734,7 @@ export async function createFightScene(config: FightSceneConfig) { } // ANVIL DROP: classic cartoon — anvil falls from sky onto opponent - async function anvilDrop(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function anvilDrop(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxSpecial() await k.wait(0.15) @@ -3766,7 +3769,7 @@ export async function createFightScene(config: FightSceneConfig) { } // POCKET CANNON: reach into pocket, pull out comically oversized gun, fire - async function pocketCannon(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function pocketCannon(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // "Reach into pocket" — arm down motion atk.play('special') await k.wait(0.12) @@ -3866,7 +3869,7 @@ export async function createFightScene(config: FightSceneConfig) { } // GRAPPLE FLURRY: both bots close in and trade sustained blows without splitting apart - async function grappleFlurry(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function grappleFlurry(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Both rush to center — meet in the middle const meetX = (origAX + origDX) / 2 const atkMeetX = meetX - dir * 25 @@ -3928,7 +3931,7 @@ export async function createFightScene(config: FightSceneConfig) { } // BODY SLAM: wrestling-style grab, lift, and slam - async function bodySlam(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function bodySlam(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Rush in close sfxSpecial() const contactX = def.pos.x - dir * 30 @@ -3978,7 +3981,7 @@ export async function createFightScene(config: FightSceneConfig) { } // PINBALL COMBO: bounce the opponent between walls/edges like a pinball - async function pinballCombo(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function pinballCombo(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Initial launch punch atk.play('attack') sfxPunch() @@ -4026,7 +4029,7 @@ export async function createFightScene(config: FightSceneConfig) { } // SUPLEX: grab from behind, lift, and drive headfirst into ground - async function suplex(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function suplex(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Dash behind defender sfxSpecial() const behindX = def.pos.x + dir * 30 @@ -4379,7 +4382,7 @@ export async function createFightScene(config: FightSceneConfig) { // === DEFENSE / SHIELD SYSTEM === async function playBlock(side: 'a' | 'b') { - const defender = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] + const defender = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter if (!defender) return const dir = side === 'a' ? 1 : -1 // Spawn shield in front @@ -5246,7 +5249,7 @@ export async function createFightScene(config: FightSceneConfig) { // ============================================================ // --- TIER 2+ ULTIMATES (Silver) — flashy multi-hits --- - async function ultimateRapidBarrage(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateRapidBarrage(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Rush in and land 8 alternating punches + kicks with screen shake const contactX = origDX - dir * 45 sfxZoomWhoosh() @@ -5275,7 +5278,7 @@ export async function createFightScene(config: FightSceneConfig) { atk.play('idle') } - async function ultimateOrbitalStrike(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateOrbitalStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Call down 5 orbital beams on the defender's position atk.play('special'); sfxSpecial() for (let i = 0; i < 5; i++) { @@ -5291,7 +5294,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.wait(0.3); atk.play('idle') } - async function ultimateCloneSurround(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateCloneSurround(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Spawn 4 clones around the defender, all attack simultaneously const clones: any[] = [] const positions = [ @@ -5316,7 +5319,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.wait(0.3) } - async function ultimateGatlingFury(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateGatlingFury(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Pull out massive gatling gun, fire 12 shots with screen shake atk.play('special') const gunLen = 60 @@ -5343,7 +5346,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.wait(0.3); atk.play('idle') } - async function ultimateEarthquake(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateEarthquake(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Jump high, slam ground, cracks + shockwaves across screen sfxZoomWhoosh() await k.tween(atk.pos.y, GROUND_Y - 150, 0.2, (v) => { atk.pos.y = v }, k.easings.easeOutQuad) @@ -5373,7 +5376,7 @@ export async function createFightScene(config: FightSceneConfig) { const ultimateMeteorShower = makeSwarm('#ff6600', 8, 10, 500, true) // --- TIER 3+ ULTIMATES (Gold) — multi-phase, full screen --- - async function ultimateScreenNuke(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateScreenNuke(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Charge up glowing orb, launch it, entire screen whites out atk.play('special'); sfxSpecial() const orb = k.add([k.circle(5), k.pos(atk.pos.x + dir * 20, atk.pos.y - 30), k.color(safeColor(k,'#ffffff')), k.opacity(0.8), k.z(18)]) @@ -5398,7 +5401,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.wait(0.4); atk.play('idle') } - async function ultimateBlackHoleVortex(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateBlackHoleVortex(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Summon black hole that pulls defender in, crushes, and ejects atk.play('special'); sfxZap() const bh = k.add([k.circle(8), k.pos(W / 2, GROUND_Y - 50), k.anchor('center'), k.color(safeColor(k,'#220044')), k.opacity(0.9), k.z(20)]) @@ -5429,7 +5432,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.wait(0.2); atk.play('idle') } - async function ultimateDimensionalRift(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateDimensionalRift(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Open rift, attacker disappears, attacks from multiple portals sfxZap(); atk.opacity = 0 const colors = ['#ff2d7b', '#00f0ff', '#39ff14', '#b83dff'] @@ -5460,7 +5463,7 @@ export async function createFightScene(config: FightSceneConfig) { atk.play('idle') } - async function ultimateTimeSlow(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateTimeSlow(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Everything goes slow-mo, attacker lands 6 devastating hits in "frozen" time scanlineGlitch(0.1) // Dim screen @@ -5486,7 +5489,7 @@ export async function createFightScene(config: FightSceneConfig) { atk.play('idle') } - async function ultimateSummonArmy(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateSummonArmy(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Summon an army of 8 mini clones that charge across the screen atk.play('special'); sfxSpecial() const army: any[] = [] @@ -5516,7 +5519,7 @@ export async function createFightScene(config: FightSceneConfig) { const ultimateAvalancheDrop = makeExplosion(30, 30, '#aaeeff', 0.2, '#ffffff', true) // ULTIMATE BULLET TIME BARRAGE — full slow-mo cinematic: dodge rain of projectiles, afterimage rush, multi-hit counter - async function ultimateBulletTimeBarrage(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateBulletTimeBarrage(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Phase 1: World goes dark, heavy slow-mo overlay const dim = k.add([k.rect(W, H), k.pos(0, 0), k.color(safeColor(k, '#000022')), k.opacity(0), k.z(25)]) await k.tween(0, 0.55, 0.2, (v) => { dim.opacity = v }) @@ -5620,7 +5623,7 @@ export async function createFightScene(config: FightSceneConfig) { } // --- TIER 4+ ULTIMATES (Platinum) — full screen, multi-phase spectacles --- - async function ultimateFinaleRush(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateFinaleRush(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Full-screen dash through, reverse, dash again, then combo finish sfxZoomWhoosh() // First pass @@ -5649,7 +5652,7 @@ export async function createFightScene(config: FightSceneConfig) { atk.play('idle') } - async function ultimateMeteorCrash(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateMeteorCrash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Attacker launches into sky, becomes a meteor, crashes into arena sfxZoomWhoosh(); atk.play('special') await k.tween(atk.pos.y, -100, 0.2, (v) => { atk.pos.y = v }, k.easings.easeInQuad) @@ -5682,7 +5685,7 @@ export async function createFightScene(config: FightSceneConfig) { atk.play('idle') } - async function ultimateScreenShatter(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateScreenShatter(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Hit so hard the "screen" shatters with fragment overlay const contactX = origDX - dir * 45 await k.tween(atk.pos.x, contactX, 0.1, (v) => { atk.pos.x = v }, k.easings.easeOutQuad) @@ -5709,7 +5712,7 @@ export async function createFightScene(config: FightSceneConfig) { atk.play('idle') } - async function ultimateGravityReverse(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateGravityReverse(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Flip gravity — defender floats up, gets juggled, slammed back down atk.play('special'); sfxZap() // Defender floats up @@ -5733,7 +5736,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.wait(0.3); atk.play('idle') } - async function ultimateInfiniteCombo(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateInfiniteCombo(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // 15-hit combo with escalating screen effects const contactX = origDX - dir * 40 sfxZoomWhoosh() @@ -5769,7 +5772,7 @@ export async function createFightScene(config: FightSceneConfig) { const ultimateRocketBarrage = makeSwarm('#ff6600', 10, 14, 550, false) // --- TIER 5+ ULTIMATES (Diamond/Legend) — absolutely insane full-screen --- - async function ultimateArmageddon(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateArmageddon(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Full screen goes dark, rain of fire, massive explosion, screen flash const darkness = k.add([k.rect(W, H), k.pos(0, 0), k.color(safeColor(k,'#000000')), k.opacity(0), k.z(25)]) await k.tween(0, 0.7, 0.3, (v) => { darkness.opacity = v }) @@ -5797,7 +5800,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.wait(0.5); atk.play('idle') } - async function ultimateOmegaBeam(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateOmegaBeam(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Charge up, fire a massive full-width beam that covers half the screen atk.play('special'); sfxSpecial() // Charge particles converging on attacker @@ -5829,7 +5832,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.wait(0.2); atk.play('idle') } - async function ultimateBeyondTheScreen(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateBeyondTheScreen(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Attacker punches defender so hard they "break" off-screen, then gravity brings them back const contactX = origDX - dir * 45 sfxZoomWhoosh() @@ -5860,7 +5863,7 @@ export async function createFightScene(config: FightSceneConfig) { atk.play('idle') } - async function ultimateMatrixDodge(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateMatrixDodge(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Matrix-style slow-mo: attacker fires 3 projectiles, "dodges" in slo-mo, then counters atk.play('special') // Fire 3 slow projectiles @@ -5891,7 +5894,7 @@ export async function createFightScene(config: FightSceneConfig) { atk.play('idle') } - async function ultimateWorldEnder(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateWorldEnder(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // The ultimate ultimate: 3-phase attack — air combo, ground slam, finishing beam // Phase 1: Launch defender into air const contactX = origDX - dir * 45 @@ -5945,7 +5948,7 @@ export async function createFightScene(config: FightSceneConfig) { // THE CREATOR — Exclusive choreographies (bitcoin-themed) // ═══════════════════════════════════════════════════════ - async function bitcoinRain(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function bitcoinRain(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxSpecial() await k.wait(0.2) @@ -5987,7 +5990,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function lightningInvoice(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function lightningInvoice(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxZap() await k.wait(0.15) @@ -6031,7 +6034,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function satoshiStrike(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function satoshiStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxSpecial() await k.wait(0.1) @@ -6069,7 +6072,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function hashRateOverload(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function hashRateOverload(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxSpecial() // Screen fills with scrolling hash text (simulated with falling green/gold rects) @@ -6105,7 +6108,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function blockchainSlam(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function blockchainSlam(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { const contactX = origDX - dir * 60 sfxZoomWhoosh() await k.tween(atk.pos.x, contactX, 0.12, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad) @@ -6144,7 +6147,7 @@ export async function createFightScene(config: FightSceneConfig) { atk.play('idle') } - async function creatorMode(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function creatorMode(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { atk.play('special') sfxSpecial() // Typing effect — rapid small flashes from attacker @@ -6181,7 +6184,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function morphStrike(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function morphStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Flash golden — the morph is handled by the morph system // This move itself is a quick dash-hit with gold sparks screenFlash('#ffd700', 0.08) @@ -6205,7 +6208,7 @@ export async function createFightScene(config: FightSceneConfig) { atk.play('idle') } - async function divineIntervention(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function divineIntervention(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Ascend off screen sfxZoomWhoosh() atk.play('win') @@ -6250,7 +6253,7 @@ export async function createFightScene(config: FightSceneConfig) { // ── Creator ₿ Swarm Variations ── - async function bitcoinSwarm(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function bitcoinSwarm(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Swarm of ₿ letters fly from creator toward defender with zigzag paths atk.play('special') sfxSpecial() @@ -6300,7 +6303,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function satoshiTornado(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function satoshiTornado(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // ₿ letters spiral around the defender in a tightening vortex then explode atk.play('special') sfxSpecial() @@ -6360,7 +6363,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function hodlWave(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function hodlWave(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Wall of ₿ letters advancing across the screen like a tidal wave atk.play('special') sfxSpecial() @@ -6414,7 +6417,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function bitcoinBarrage(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) { + async function bitcoinBarrage(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) { // Rapid-fire ₿ letters shot from the creator's laptop like bullets atk.play('special') sfxSpecial() @@ -6462,7 +6465,7 @@ export async function createFightScene(config: FightSceneConfig) { // ── Creator Ultimates ── - async function ultimateGenesisBlock(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateGenesisBlock(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Screen goes dark, massive genesis block materializes and detonates atk.play('special') sfxSpecial() @@ -6495,7 +6498,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function ultimateTheHalving(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateTheHalving(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Screen splits horizontally with a golden line atk.play('special') sfxZap() @@ -6528,7 +6531,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function ultimateFullNodeCrush(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateFullNodeCrush(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Giant server rack drops from above atk.play('special') sfxSpecial() @@ -6567,7 +6570,7 @@ export async function createFightScene(config: FightSceneConfig) { await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad) } - async function ultimateSatoshiVision(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) { + async function ultimateSatoshiVision(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) { // Everything turns gold — defender trapped in bitcoin block atk.play('special') sfxSpecial() @@ -7179,7 +7182,7 @@ export async function createFightScene(config: FightSceneConfig) { } function showSpeechBubble(side: 'a' | 'b', text: string, duration: number = 3) { - const fighter = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] + const fighter = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter if (!fighter || !text) return // Kill previous bubble on this side @@ -7332,7 +7335,7 @@ export async function createFightScene(config: FightSceneConfig) { function startTalking(side: 'a' | 'b') { stopTalking(side) // cleanup any existing - const fighter = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] + const fighter = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter if (!fighter?.exists()) return const mouthColor = side === 'a' ? '#00f0ff' : '#ff2d7b' @@ -7510,7 +7513,7 @@ export async function createFightScene(config: FightSceneConfig) { async function humanJumpsIn(side: 'a' | 'b') { const fighterTag = side === 'a' ? 'fighterA' : 'fighterB' const spriteId = side === 'a' ? 'humanA' : 'humanB' - const fighter = k.get(fighterTag)[0] + const fighter = k.get(fighterTag)[0] as Fighter if (!fighter) return // Spawn human at fighter position @@ -8628,8 +8631,8 @@ export async function createFightScene(config: FightSceneConfig) { }, async playEntrance() { - const fA = k.get('fighterA')[0] - const fB = k.get('fighterB')[0] + const fA = k.get('fighterA')[0] as Fighter + const fB = k.get('fighterB')[0] as Fighter if (!fA || !fB) return const entrances = [ @@ -9170,8 +9173,8 @@ export async function createFightScene(config: FightSceneConfig) { // Helper: reset both fighters to home positions async _resetPositions() { - const fA = k.get('fighterA')[0] - const fB = k.get('fighterB')[0] + const fA = k.get('fighterA')[0] as Fighter + const fB = k.get('fighterB')[0] as Fighter if (!fA || !fB) return await Promise.all([ k.tween(fA.pos.x, HOME_A, 0.2, (v) => { fA.pos.x = v }, k.easings.easeOutQuad), @@ -9185,8 +9188,8 @@ export async function createFightScene(config: FightSceneConfig) { // 0: RAPID BRAWL — close distance, trade rapid blows async _brawlRapid(winningSide: 'a' | 'b' | null, intensity: number) { - const fA = k.get('fighterA')[0] - const fB = k.get('fighterB')[0] + const fA = k.get('fighterA')[0] as Fighter + const fB = k.get('fighterB')[0] as Fighter if (!fA || !fB) return const midX = W / 2 sfxZoomWhoosh() @@ -9211,8 +9214,8 @@ export async function createFightScene(config: FightSceneConfig) { // 1: KNOCKDOWN BRAWL — combo ends with loser knocked flat, has to get back up async _brawlKnockdown(winningSide: 'a' | 'b' | null, intensity: number) { - const fA = k.get('fighterA')[0] - const fB = k.get('fighterB')[0] + const fA = k.get('fighterA')[0] as Fighter + const fB = k.get('fighterB')[0] as Fighter if (!fA || !fB) return const winner = winningSide === 'a' ? fA : winningSide === 'b' ? fB : (Math.random() > 0.5 ? fA : fB) const loser = winner === fA ? fB : fA @@ -9261,8 +9264,8 @@ export async function createFightScene(config: FightSceneConfig) { // 2: WALL BOUNCE — punch sends fighter flying into screen edge, bounces off async _brawlWallBounce(winningSide: 'a' | 'b' | null, intensity: number) { - const fA = k.get('fighterA')[0] - const fB = k.get('fighterB')[0] + const fA = k.get('fighterA')[0] as Fighter + const fB = k.get('fighterB')[0] as Fighter if (!fA || !fB) return const winner = winningSide === 'a' ? fA : winningSide === 'b' ? fB : (Math.random() > 0.5 ? fA : fB) const loser = winner === fA ? fB : fA @@ -9320,8 +9323,8 @@ export async function createFightScene(config: FightSceneConfig) { // 3: DIZZY STAGGER — sustained combo leaves loser wobbling around the screen async _brawlDizzyStagger(winningSide: 'a' | 'b' | null, intensity: number) { - const fA = k.get('fighterA')[0] - const fB = k.get('fighterB')[0] + const fA = k.get('fighterA')[0] as Fighter + const fB = k.get('fighterB')[0] as Fighter if (!fA || !fB) return const winner = winningSide === 'a' ? fA : winningSide === 'b' ? fB : (Math.random() > 0.5 ? fA : fB) const loser = winner === fA ? fB : fA @@ -9372,8 +9375,8 @@ export async function createFightScene(config: FightSceneConfig) { // 4: SUPLEX SLAM — grab, lift overhead, slam to ground with bounce async _brawlSuplex(attackerSide: 'a' | 'b', intensity: number) { - const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] - const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] + const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter + const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter if (!attacker || !defender) return const dir = attackerSide === 'a' ? 1 : -1 @@ -9423,8 +9426,8 @@ export async function createFightScene(config: FightSceneConfig) { // 5: PING PONG VOLLEY — fighters knock each other back and forth across the screen async _brawlPingPong(winningSide: 'a' | 'b' | null, intensity: number) { - const fA = k.get('fighterA')[0] - const fB = k.get('fighterB')[0] + const fA = k.get('fighterA')[0] as Fighter + const fB = k.get('fighterB')[0] as Fighter if (!fA || !fB) return const volleys = 3 + Math.floor(intensity * 3) + Math.floor(Math.random() * 2) // 3-8 @@ -9471,8 +9474,8 @@ export async function createFightScene(config: FightSceneConfig) { // 6: GROUND AND POUND — attacker pins defender, hits them on the ground async _brawlGroundPound(attackerSide: 'a' | 'b', intensity: number) { - const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] - const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] + const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter + const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter if (!attacker || !defender) return const dir = attackerSide === 'a' ? 1 : -1 @@ -9515,8 +9518,8 @@ export async function createFightScene(config: FightSceneConfig) { // 7: HAYMAKER — slow dramatic wind-up, pause, devastating single hit with knockdown async _brawlHaymaker(attackerSide: 'a' | 'b', _intensity: number) { - const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] - const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] + const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter + const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter if (!attacker || !defender) return const dir = attackerSide === 'a' ? 1 : -1 @@ -9563,8 +9566,8 @@ export async function createFightScene(config: FightSceneConfig) { // 8: BODY CHECK BOUNCER — both charge, collide, one bounces off the other async _brawlBodyCheck(winningSide: 'a' | 'b' | null, _intensity: number) { - const fA = k.get('fighterA')[0] - const fB = k.get('fighterB')[0] + const fA = k.get('fighterA')[0] as Fighter + const fB = k.get('fighterB')[0] as Fighter if (!fA || !fB) return const winner = winningSide === 'a' ? fA : winningSide === 'b' ? fB : (Math.random() > 0.5 ? fA : fB) const loser = winner === fA ? fB : fA @@ -9625,8 +9628,8 @@ export async function createFightScene(config: FightSceneConfig) { // Clinch combo: grapple at close range, one fighter dominates with sustained contact async _clinchCombo(attackerSide: 'a' | 'b', intensity: number) { - const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] - const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] + const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter + const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter if (!attacker || !defender) return const dir = attackerSide === 'a' ? 1 : -1 const origAX = attackerSide === 'a' ? HOME_A : HOME_B @@ -9664,8 +9667,8 @@ export async function createFightScene(config: FightSceneConfig) { // Counter-attack: after a choreography hit, the defender retaliates briefly async _counterAttack(defenderSide: 'a' | 'b') { - const defender = k.get(defenderSide === 'a' ? 'fighterA' : 'fighterB')[0] - const attacker = k.get(defenderSide === 'a' ? 'fighterB' : 'fighterA')[0] + const defender = k.get(defenderSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter + const attacker = k.get(defenderSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter if (!defender || !attacker) return const dir = defenderSide === 'a' ? 1 : -1 const origDX = defenderSide === 'a' ? HOME_A : HOME_B @@ -9683,8 +9686,8 @@ export async function createFightScene(config: FightSceneConfig) { }, async playAttack(side: 'a' | 'b', choreographyName: string, isCritical: boolean) { - const attacker = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] - const defender = k.get(side === 'a' ? 'fighterB' : 'fighterA')[0] + const attacker = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter + const defender = k.get(side === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter if (!attacker || !defender) return const origAX = side === 'a' ? HOME_A : HOME_B @@ -9705,8 +9708,8 @@ export async function createFightScene(config: FightSceneConfig) { }, async playRound(event: RoundEvent) { - const fA = k.get('fighterA')[0] - const fB = k.get('fighterB')[0] + const fA = k.get('fighterA')[0] as Fighter + const fB = k.get('fighterB')[0] as Fighter if (!fA || !fB) { await k.wait(0.5); return } // Sprites missing (mobile load failure) — skip animation const aWon = event.winnerId === event.botAId @@ -9932,7 +9935,7 @@ export async function createFightScene(config: FightSceneConfig) { : (isUltimate || (exchangeCritical && intensity > 0.7 && Math.random() < 0.4)) let morphRevert: (() => Promise) | null = null if (shouldMorph) { - const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] + const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter if (attacker) { if (isCreatorFighter) { // Omni-morph: creator transforms into a random archetype (sprite swap) @@ -10010,7 +10013,7 @@ export async function createFightScene(config: FightSceneConfig) { } // RGB glitch + hyperspeed lines on critical final blow glitchRGB(0.3) - const defPos = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] + const defPos = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter if (defPos) hyperSpeedLines(defPos.pos.x, defPos.pos.y - 30, 0.4) } @@ -10246,8 +10249,8 @@ export async function createFightScene(config: FightSceneConfig) { // Respect nod between fighters on close rounds (10% when margin <= 1) if (margin <= 1 && Math.random() < 0.1) { - const fA2 = k.get('fighterA')[0] - const fB2 = k.get('fighterB')[0] + const fA2 = k.get('fighterA')[0] as Fighter + const fB2 = k.get('fighterB')[0] as Fighter if (fA2 && fB2) { spawnEmoteText(fA2.pos.x, fA2.pos.y - 45, respectLines[Math.floor(Math.random() * respectLines.length)], '#88ccff') await k.wait(0.3) @@ -10265,7 +10268,7 @@ export async function createFightScene(config: FightSceneConfig) { }, async playTaunt(side: 'a' | 'b') { - const taunter = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] + const taunter = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter if (!taunter) return // Creator taunts come with menacing voice lines const taunterArch = side === 'a' ? botA.archetype : botB.archetype @@ -10307,7 +10310,7 @@ export async function createFightScene(config: FightSceneConfig) { }, async playDodge(side: 'a' | 'b') { - const dodger = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] + const dodger = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter if (!dodger) return const origX = side === 'a' ? HOME_A : HOME_B const origY = dodger.pos.y @@ -10324,8 +10327,8 @@ export async function createFightScene(config: FightSceneConfig) { }, async playKO(winningSide: 'a' | 'b', winnerName: string) { - const loser = k.get(winningSide === 'a' ? 'fighterB' : 'fighterA')[0] - const winner = k.get(winningSide === 'a' ? 'fighterA' : 'fighterB')[0] + const loser = k.get(winningSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter + const winner = k.get(winningSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter if (!loser || !winner) return const dir = winningSide === 'a' ? 1 : -1 @@ -11183,8 +11186,8 @@ export async function createFightScene(config: FightSceneConfig) { }, async playPerfect(winningSide: 'a' | 'b', winnerName: string) { - const winner = k.get(winningSide === 'a' ? 'fighterA' : 'fighterB')[0] - const loser = k.get(winningSide === 'a' ? 'fighterB' : 'fighterA')[0] + const winner = k.get(winningSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter + const loser = k.get(winningSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter if (!loser || !winner) return // Judge goes wild for a perfect