// Fun Stuff - CLIENT animations. COPY TO kubejs/client_scripts/funstuff_client.js
// Same architecture as the Grand Line client script: fail-closed third-person
// guard, every keyframe track ends at 0, poses blend with the vanilla swing.
console.log('[Fun Stuff] client animation script loading...')

function fsLerp(a, b, t) { return a + (b - a) * t }
function fsEase(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2 }
function fsTpOnly(builder) {
    try { return !builder.isFirstPerson() } catch (e) {
        try { return ('' + builder.getFirstPersonContext()) == 'NONE' } catch (e2) { return false }
    }
}
function fsProg(player, powerId, abilityKey, duration) {
    try {
        var entry = null
        try { entry = palladium.abilities.getEntry(player, powerId, abilityKey) } catch (e) {}
        if (entry != null) {
            try {
                var ticks = entry.getEnabledTicks()
                if (ticks > 0) return Math.min(ticks / duration, 1.0)
            } catch (e) {}
        }
        if (palladium.abilities.isEnabled(player, powerId, abilityKey)) return 0.5
        return -1
    } catch (e) { return -1 }
}
function fsApply(builder, keyframes, progress) {
    if (progress !== progress) return false
    if (progress < 0) return false
    var p = Math.max(0, Math.min(1, progress))
    var k1 = keyframes[0], k2 = keyframes[keyframes.length - 1]
    for (var i = 0; i < keyframes.length - 1; i++) {
        if (p >= keyframes[i].t && p <= keyframes[i + 1].t) { k1 = keyframes[i]; k2 = keyframes[i + 1]; break }
    }
    var span = k2.t - k1.t
    var segT = span > 0 ? (p - k1.t) / span : 0
    var eased = fsEase(segT)
    var names = {}
    for (var key in k1.parts) names[key] = true
    for (var key in k2.parts) names[key] = true
    for (var partName in names) {
        var p1 = k1.parts[partName] || { x: 0, y: 0, z: 0 }
        var p2 = k2.parts[partName] || { x: 0, y: 0, z: 0 }
        try {
            var part = builder.get(partName)
            if (part == null) continue
            if (p2.x !== undefined) part.setXRotDegrees(fsLerp(p1.x || 0, p2.x, eased))
            if (p2.y !== undefined) part.setYRotDegrees(fsLerp(p1.y || 0, p2.y, eased))
            if (p2.z !== undefined) part.setZRotDegrees(fsLerp(p1.z || 0, p2.z, eased))
        } catch (e) {}
    }
    return true
}

PalladiumEvents.registerAnimations((event) => {
    console.log('[Fun Stuff] registerAnimations')

    // KICKFLIP (priority 160): a HOP - knees pop up and the feet FLICK to the toe
    // side (board flips under you). No body spin - that's the shove-it's job.
    var FLIP_KF = [
        { t: 0.0, parts: {} },
        { t: 0.3, parts: { BODY: { x: 15 }, RIGHT_LEG: { x: 62, z: -24 }, LEFT_LEG: { x: 54, z: -20 }, RIGHT_ARM: { z: 46, x: -14 }, LEFT_ARM: { z: -46, x: -14 }, HEAD: { x: 8 } } },
        { t: 0.6, parts: { BODY: { x: 9 }, RIGHT_LEG: { x: 46, z: 16 }, LEFT_LEG: { x: 40, z: 12 }, RIGHT_ARM: { z: 40 }, LEFT_ARM: { z: -40 } } },
        { t: 1.0, parts: { BODY: { x: 0 }, RIGHT_LEG: { x: 0, z: 0 }, LEFT_LEG: { x: 0, z: 0 }, RIGHT_ARM: { z: 0, x: 0 }, LEFT_ARM: { z: 0, x: 0 }, HEAD: { x: 0 } } }
    ]
    event.register('fs_kickflip', 160, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var player = builder.getPlayer()
            var prog = fsProg(player, 'funstuff:fs_sk8', 'kickflip', 12)
            if (prog >= 0 && prog < 1.0) { fsApply(builder, FLIP_KF, prog); return }
        } catch (e) {}
    })

    // POWERSLIDE (priority 159): low crouched carve lean
    var SLIDE_KF = [
        { t: 0.0, parts: { BODY: { z: 0 } } },
        { t: 0.3, parts: { BODY: { z: 18, x: 10 }, RIGHT_LEG: { x: 35 }, LEFT_LEG: { x: 25 }, RIGHT_ARM: { z: 55 }, LEFT_ARM: { z: -25 }, HEAD: { z: -12 } } },
        { t: 0.7, parts: { BODY: { z: 14, x: 8 }, RIGHT_LEG: { x: 30 }, LEFT_LEG: { x: 20 }, RIGHT_ARM: { z: 45 }, LEFT_ARM: { z: -20 }, HEAD: { z: -10 } } },
        { t: 1.0, parts: { BODY: { z: 0, x: 0 }, RIGHT_LEG: { x: 0 }, LEFT_LEG: { x: 0 }, RIGHT_ARM: { z: 0 }, LEFT_ARM: { z: 0 }, HEAD: { z: 0 } } }
    ]
    event.register('fs_slide', 159, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var prog = fsProg(builder.getPlayer(), 'funstuff:fs_sk8', 'powerslide', 10)
            if (prog >= 0 && prog < 1.0) { fsApply(builder, SLIDE_KF, prog); return }
        } catch (e) {}
    })

    // HOVERBOARD BOOST (priority 159): crouch low + lean into the zoom
    var HBOOST_KF = [
        { t: 0.0, parts: { BODY: { x: 0 } } },
        { t: 0.3, parts: { BODY: { x: 26 }, HEAD: { x: -18 }, RIGHT_ARM: { x: -35, z: 20 }, LEFT_ARM: { x: -35, z: -20 }, RIGHT_LEG: { x: 20 }, LEFT_LEG: { x: -14 } } },
        { t: 0.8, parts: { BODY: { x: 22 }, HEAD: { x: -16 }, RIGHT_ARM: { x: -30, z: 18 }, LEFT_ARM: { x: -30, z: -18 }, RIGHT_LEG: { x: 18 }, LEFT_LEG: { x: -12 } } },
        { t: 1.0, parts: { BODY: { x: 0 }, HEAD: { x: 0 }, RIGHT_ARM: { x: 0, z: 0 }, LEFT_ARM: { x: 0, z: 0 }, RIGHT_LEG: { x: 0 }, LEFT_LEG: { x: 0 } } }
    ]
    event.register('fs_hoverboost', 159, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var prog = fsProg(builder.getPlayer(), 'funstuff:fs_hover', 'boost', 12)
            if (prog >= 0 && prog < 1.0) { fsApply(builder, HBOOST_KF, prog); return }
        } catch (e) {}
    })

    // HEELFLIP (priority 160): a HOP where the knees splay OUT and the heels kick
    // the opposite way to a kickflip - legs open wide, arms drop. Still no spin.
    var FLIP_HF = [
        { t: 0.0, parts: {} },
        { t: 0.3, parts: { BODY: { x: 12 }, RIGHT_LEG: { x: 50, z: 34 }, LEFT_LEG: { x: 46, z: -34 }, RIGHT_ARM: { z: -30, x: -8 }, LEFT_ARM: { z: 30, x: -8 }, HEAD: { x: 6 } } },
        { t: 0.6, parts: { BODY: { x: 7 }, RIGHT_LEG: { x: 38, z: 20 }, LEFT_LEG: { x: 34, z: -20 }, RIGHT_ARM: { z: -22 }, LEFT_ARM: { z: 22 } } },
        { t: 1.0, parts: { BODY: { x: 0 }, RIGHT_LEG: { x: 0, z: 0 }, LEFT_LEG: { x: 0, z: 0 }, RIGHT_ARM: { z: 0, x: 0 }, LEFT_ARM: { z: 0, x: 0 }, HEAD: { x: 0 } } }
    ]
    event.register('fs_heelflip', 160, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var prog = fsProg(builder.getPlayer(), 'funstuff:fs_sk8', 'heelflip', 12)
            if (prog >= 0 && prog < 1.0) { fsApply(builder, FLIP_HF, prog); return }
        } catch (e) {}
    })

    // 360 SHOVE-IT (priority 160): fast full spin, feet-led, arms flared
    var SHUV_KF = [
        { t: 0.0, parts: { BODY: { y: 0 } } },
        { t: 0.4, parts: { BODY: { y: 200 }, RIGHT_LEG: { x: 40, y: 12 }, LEFT_LEG: { x: 40, y: -12 }, RIGHT_ARM: { z: 45, x: -10 }, LEFT_ARM: { z: -45, x: -10 }, HEAD: { y: -30 } } },
        { t: 0.75, parts: { BODY: { y: 330 }, RIGHT_LEG: { x: 30 }, LEFT_LEG: { x: 30 }, RIGHT_ARM: { z: 30 }, LEFT_ARM: { z: -30 } } },
        { t: 1.0, parts: { BODY: { y: 360 }, RIGHT_LEG: { x: 0, y: 0 }, LEFT_LEG: { x: 0, y: 0 }, RIGHT_ARM: { z: 0, x: 0 }, LEFT_ARM: { z: 0, x: 0 }, HEAD: { y: 0 } } }
    ]
    event.register('fs_shuvit', 160, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var prog = fsProg(builder.getPlayer(), 'funstuff:fs_sk8', 'shuvit', 14)
            if (prog >= 0 && prog < 1.0) { fsApply(builder, SHUV_KF, prog); return }
        } catch (e) {}
    })

    // MANUAL (priority 159): wheelie - lean back, arms out for balance (held)
    var MANUAL_KF = [
        { t: 0.0, parts: { BODY: { x: 0 } } },
        { t: 0.2, parts: { BODY: { x: -20 }, RIGHT_LEG: { x: -18 }, LEFT_LEG: { x: 8 }, RIGHT_ARM: { z: 50, x: -10 }, LEFT_ARM: { z: -50, x: -10 }, HEAD: { x: -8 } } },
        { t: 0.85, parts: { BODY: { x: -22 }, RIGHT_LEG: { x: -20 }, LEFT_LEG: { x: 10 }, RIGHT_ARM: { z: 48, x: -8 }, LEFT_ARM: { z: -48, x: -8 }, HEAD: { x: -8 } } },
        { t: 1.0, parts: { BODY: { x: 0 }, RIGHT_LEG: { x: 0 }, LEFT_LEG: { x: 0 }, RIGHT_ARM: { z: 0, x: 0 }, LEFT_ARM: { z: 0, x: 0 }, HEAD: { x: 0 } } }
    ]
    event.register('fs_manual', 159, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var prog = fsProg(builder.getPlayer(), 'funstuff:fs_sk8', 'manual', 20)
            if (prog >= 0 && prog < 1.0) { fsApply(builder, MANUAL_KF, prog); return }
        } catch (e) {}
    })

    // BACKFLIP (priority 160): big backward tuck rotation
    var BFLIP_KF = [
        { t: 0.0, parts: { BODY: { x: 0 }, RIGHT_LEG: { x: 0 }, LEFT_LEG: { x: 0 } } },
        { t: 0.2, parts: { BODY: { x: -90 }, RIGHT_LEG: { x: 75 }, LEFT_LEG: { x: 75 }, RIGHT_ARM: { x: 30, z: -20 }, LEFT_ARM: { x: 30, z: 20 }, HEAD: { x: -25 } } },
        { t: 0.5, parts: { BODY: { x: -200 }, RIGHT_LEG: { x: 80 }, LEFT_LEG: { x: 80 }, RIGHT_ARM: { x: 10, z: -15 }, LEFT_ARM: { x: 10, z: 15 }, HEAD: { x: -25 } } },
        { t: 0.8, parts: { BODY: { x: -320 }, RIGHT_LEG: { x: 45 }, LEFT_LEG: { x: 45 }, HEAD: { x: -15 } } },
        { t: 1.0, parts: { BODY: { x: -360 }, RIGHT_LEG: { x: 0 }, LEFT_LEG: { x: 0 }, RIGHT_ARM: { x: 0, z: 0 }, LEFT_ARM: { x: 0, z: 0 }, HEAD: { x: 0 } } }
    ]
    event.register('fs_backflip', 160, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var prog = fsProg(builder.getPlayer(), 'funstuff:fs_sk8', 'backflip', 18)
            if (prog >= 0 && prog < 1.0) { fsApply(builder, BFLIP_KF, prog); return }
        } catch (e) {}
    })

    // RANDOM AIR TRICK on a normal spacebar jump (priority 71): every time you
    // leave the ground on a skateboard a RANDOM aerial pose plays (never the same
    // one twice running). Purely cosmetic - no ability/keypress needed.
    var FS_AIR_TRICKS = [
        [ { t: 0, parts: {} }, { t: 0.35, parts: { RIGHT_LEG: { x: 72 }, LEFT_LEG: { x: 58 }, RIGHT_ARM: { x: -35, z: -28 }, LEFT_ARM: { x: -28, z: 28 }, BODY: { x: 12 }, HEAD: { x: 8 } } }, { t: 1, parts: { RIGHT_LEG: { x: 0 }, LEFT_LEG: { x: 0 }, RIGHT_ARM: { x: 0, z: 0 }, LEFT_ARM: { x: 0, z: 0 }, BODY: { x: 0 }, HEAD: { x: 0 } } } ],
        [ { t: 0, parts: { BODY: { y: 0 } } }, { t: 0.5, parts: { BODY: { y: 190 }, RIGHT_LEG: { x: 45 }, LEFT_LEG: { x: 45 }, RIGHT_ARM: { z: 35 }, LEFT_ARM: { z: -35 } } }, { t: 1, parts: { BODY: { y: 360 }, RIGHT_LEG: { x: 0 }, LEFT_LEG: { x: 0 }, RIGHT_ARM: { z: 0 }, LEFT_ARM: { z: 0 } } } ],
        [ { t: 0, parts: {} }, { t: 0.4, parts: { BODY: { x: -16 }, RIGHT_ARM: { x: 20, z: -45 }, LEFT_ARM: { x: -55, z: 40 }, RIGHT_LEG: { x: 55 }, LEFT_LEG: { x: 70 }, HEAD: { x: -12 } } }, { t: 1, parts: { BODY: { x: 0 }, RIGHT_ARM: { x: 0, z: 0 }, LEFT_ARM: { x: 0, z: 0 }, RIGHT_LEG: { x: 0 }, LEFT_LEG: { x: 0 }, HEAD: { x: 0 } } } ],
        [ { t: 0, parts: {} }, { t: 0.4, parts: { BODY: { x: 28 }, RIGHT_ARM: { x: -15, z: -22 }, LEFT_ARM: { x: -15, z: 22 }, RIGHT_LEG: { x: 72 }, LEFT_LEG: { x: 58 }, HEAD: { x: 22 } } }, { t: 1, parts: { BODY: { x: 0 }, RIGHT_ARM: { x: 0, z: 0 }, LEFT_ARM: { x: 0, z: 0 }, RIGHT_LEG: { x: 0 }, LEFT_LEG: { x: 0 }, HEAD: { x: 0 } } } ],
        [ { t: 0, parts: { BODY: { y: 0 } } }, { t: 0.5, parts: { BODY: { y: 100 }, RIGHT_LEG: { x: 55, y: 10 }, LEFT_LEG: { x: 55, y: -10 }, RIGHT_ARM: { z: -30 }, LEFT_ARM: { z: 30 } } }, { t: 1, parts: { BODY: { y: 180 }, RIGHT_LEG: { x: 0, y: 0 }, LEFT_LEG: { x: 0, y: 0 }, RIGHT_ARM: { z: 0 }, LEFT_ARM: { z: 0 } } } ],
        [ { t: 0, parts: { BODY: { x: 0 } } }, { t: 0.5, parts: { BODY: { x: -190 }, RIGHT_LEG: { x: 78 }, LEFT_LEG: { x: 78 }, RIGHT_ARM: { x: 20, z: -15 }, LEFT_ARM: { x: 20, z: 15 }, HEAD: { x: -20 } } }, { t: 1, parts: { BODY: { x: -360 }, RIGHT_LEG: { x: 0 }, LEFT_LEG: { x: 0 }, RIGHT_ARM: { x: 0, z: 0 }, LEFT_ARM: { x: 0, z: 0 }, HEAD: { x: 0 } } } ]
    ]
    var fsAir = { up: false, idx: 0, t0: 0, f: 0, last: -1 }
    event.register('fs_airjump', 71, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var player = builder.getPlayer()
            var held = ''
            try { held = '' + player.mainHandItem.id } catch (e) {}
            if (held != 'funstuff:skateboard') return
            var airborne = false
            try { airborne = !player.isOnGround() } catch (e) {}
            if (!airborne) { fsAir.up = false; fsAir.f = 0; return }
            // NaN-safe tick clock (Rhino returns undefined for a missing prop
            // WITHOUT throwing - the same trap that broke the saber throw)
            var age = NaN
            try { if (typeof player.age === 'number') age = player.age } catch (e) {}
            if (age !== age) { try { if (typeof player.tickCount === 'number') age = player.tickCount } catch (e) {} }
            fsAir.f++
            if (!fsAir.up) {
                fsAir.up = true
                var n = FS_AIR_TRICKS.length
                var pick = Math.floor(Math.random() * n)
                if (pick == fsAir.last && n > 1) pick = (pick + 1) % n
                fsAir.idx = pick; fsAir.last = pick
                fsAir.t0 = (age === age) ? age : 0
                fsAir.f = 1
            }
            var elapsed = (age === age) ? (age - fsAir.t0) : (fsAir.f / 3)
            var prog = elapsed / 16
            if (prog !== prog) prog = 0
            if (prog > 1) prog = 1
            if (prog < 0) prog = 0
            fsApply(builder, FS_AIR_TRICKS[fsAir.idx], prog)
        } catch (e) {}
    })

    // RIDE STANCE (priority 72): holding a board on the ground = surf stance
    // (front foot forward, body angled, arms out for balance - overrides the
    // vanilla walk swing so it reads as RIDING, not walking)
    event.register('fs_ride', 72, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var player = builder.getPlayer()
            var held = ''
            try { held = '' + player.mainHandItem.id } catch (e) {}
            if (held != 'funstuff:skateboard' && held != 'funstuff:hoverboard') return
            var airborne = false
            try { airborne = !player.isOnGround() } catch (e) {}
            if (airborne) return
            builder.get('RIGHT_LEG').setXRotDegrees(24)
            builder.get('RIGHT_LEG').setYRotDegrees(14)
            builder.get('LEFT_LEG').setXRotDegrees(-16)
            builder.get('LEFT_LEG').setYRotDegrees(-12)
            builder.get('BODY').setYRotDegrees(16)
            builder.get('BODY').setXRotDegrees(6)
            builder.get('HEAD').setYRotDegrees(-14)
            builder.get('RIGHT_ARM').setZRotDegrees(30)
            builder.get('RIGHT_ARM').setXRotDegrees(10)
            builder.get('LEFT_ARM').setZRotDegrees(-26)
            builder.get('LEFT_ARM').setXRotDegrees(-12)
        } catch (e) {}
    })

    // AIR GRAB (priority 70): hoverboard + airborne = grab pose (the skateboard
    // uses the randomised fs_airjump tricks instead)
    event.register('fs_airgrab', 70, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var player = builder.getPlayer()
            var held = ''
            try { held = '' + player.mainHandItem.id } catch (e) {}
            if (held != 'funstuff:hoverboard') return
            var airborne = false
            try { airborne = !player.isOnGround() } catch (e) { }
            if (!airborne) return
            builder.get('RIGHT_LEG').setXRotDegrees(55)
            builder.get('LEFT_LEG').setXRotDegrees(35)
            builder.get('RIGHT_ARM').setXRotDegrees(25)
            builder.get('RIGHT_ARM').setZRotDegrees(-35)
            builder.get('LEFT_ARM').setXRotDegrees(-45)
            builder.get('LEFT_ARM').setZRotDegrees(30)
            builder.get('BODY').setXRotDegrees(8)
        } catch (e) {}
    })

    // GUN AIM (priority 161): arm locked forward while firing
    var AIM_KF = [
        { t: 0.0, parts: { RIGHT_ARM: { x: -50 } } },
        { t: 0.2, parts: { RIGHT_ARM: { x: -90, z: -4 }, LEFT_ARM: { x: -70, y: 25 }, HEAD: { x: 4 } } },
        { t: 0.8, parts: { RIGHT_ARM: { x: -88, z: -4 }, LEFT_ARM: { x: -68, y: 24 }, HEAD: { x: 4 } } },
        { t: 1.0, parts: { RIGHT_ARM: { x: 0, z: 0 }, LEFT_ARM: { x: 0, y: 0 }, HEAD: { x: 0 } } }
    ]
    var AIM_LIST = [
        ['funstuff:fs_ak47', 'fire', 20],
        ['funstuff:fs_glock', 'fire', 6],
        ['funstuff:fs_deagle', 'fire', 8]
    ]
    event.register('fs_gunaim', 161, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var player = builder.getPlayer()
            for (var i = 0; i < AIM_LIST.length; i++) {
                var prog = fsProg(player, AIM_LIST[i][0], AIM_LIST[i][1], AIM_LIST[i][2])
                if (prog >= 0 && prog < 1.0) { fsApply(builder, AIM_KF, prog); return }
            }
        } catch (e) {}
    })

    // RELOAD (priority 162): mag out, mag in, rack the slide
    var RELOAD_KF = [
        { t: 0.0, parts: { RIGHT_ARM: { x: 0 } } },
        { t: 0.25, parts: { RIGHT_ARM: { x: -55 }, LEFT_ARM: { x: -20, y: 30 }, HEAD: { x: 14 } } },
        { t: 0.5, parts: { RIGHT_ARM: { x: -50 }, LEFT_ARM: { x: -75, y: 10 }, HEAD: { x: 16 } } },
        { t: 0.75, parts: { RIGHT_ARM: { x: -60 }, LEFT_ARM: { x: -45, y: -10 }, HEAD: { x: 10 } } },
        { t: 1.0, parts: { RIGHT_ARM: { x: 0 }, LEFT_ARM: { x: 0, y: 0 }, HEAD: { x: 0 } } }
    ]
    var RELOAD_LIST = [
        ['funstuff:fs_ak47', 'reload', 30],
        ['funstuff:fs_glock', 'reload', 20],
        ['funstuff:fs_deagle', 'reload', 24]
    ]
    event.register('fs_reload', 162, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var player = builder.getPlayer()
            for (var i = 0; i < RELOAD_LIST.length; i++) {
                var prog = fsProg(player, RELOAD_LIST[i][0], RELOAD_LIST[i][1], RELOAD_LIST[i][2])
                if (prog >= 0 && prog < 1.0) { fsApply(builder, RELOAD_KF, prog); return }
            }
        } catch (e) {}
    })

    // FORCE PUSH (priority 163): both palms thrust forward
    var PUSH_KF = [
        { t: 0.0, parts: { RIGHT_ARM: { x: 0 }, LEFT_ARM: { x: 0 } } },
        { t: 0.3, parts: { RIGHT_ARM: { x: -88, y: -18 }, LEFT_ARM: { x: -88, y: 18 }, BODY: { x: -6 } } },
        { t: 0.7, parts: { RIGHT_ARM: { x: -92, y: -16 }, LEFT_ARM: { x: -92, y: 16 }, BODY: { x: -6 } } },
        { t: 1.0, parts: { RIGHT_ARM: { x: 0, y: 0 }, LEFT_ARM: { x: 0, y: 0 }, BODY: { x: 0 } } }
    ]
    event.register('fs_push', 163, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var player = builder.getPlayer()
            var prog = fsProg(player, 'funstuff:fs_force', 'force_push', 10)
            if (prog >= 0 && prog < 1.0) { fsApply(builder, PUSH_KF, prog); return }
        } catch (e) {}
    })

    // FORCE PULL (priority 163): reach out then drag both arms in
    var PULL_KF = [
        { t: 0.0, parts: { RIGHT_ARM: { x: 0 }, LEFT_ARM: { x: 0 } } },
        { t: 0.3, parts: { RIGHT_ARM: { x: -85, y: -25 }, LEFT_ARM: { x: -85, y: 25 }, BODY: { x: -4 } } },
        { t: 0.7, parts: { RIGHT_ARM: { x: -45, y: 8 }, LEFT_ARM: { x: -45, y: -8 }, BODY: { x: 8 } } },
        { t: 1.0, parts: { RIGHT_ARM: { x: 0, y: 0 }, LEFT_ARM: { x: 0, y: 0 }, BODY: { x: 0 } } }
    ]
    event.register('fs_pull', 163, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var player = builder.getPlayer()
            var prog = fsProg(player, 'funstuff:fs_force', 'force_pull', 10)
            if (prog >= 0 && prog < 1.0) { fsApply(builder, PULL_KF, prog); return }
        } catch (e) {}
    })

    // SABER THROW (priority 164): huge windmill release + body torque
    var THROW_KF = [
        { t: 0.0, parts: { RIGHT_ARM: { x: 0 } } },
        { t: 0.25, parts: { RIGHT_ARM: { x: -170, z: -10 }, BODY: { y: -28 }, HEAD: { y: 12 } } },
        { t: 0.5, parts: { RIGHT_ARM: { x: -30, z: 35 }, BODY: { y: 26 }, HEAD: { y: -10 } } },
        { t: 0.8, parts: { RIGHT_ARM: { x: -60, z: 10 }, BODY: { y: 8 } } },
        { t: 1.0, parts: { RIGHT_ARM: { x: 0, z: 0 }, BODY: { y: 0 }, HEAD: { y: 0 } } }
    ]
    event.register('fs_throw', 164, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var player = builder.getPlayer()
            var prog = fsProg(player, 'funstuff:fs_force', 'saber_throw', 12)
            if (prog >= 0 && prog < 1.0) { fsApply(builder, THROW_KF, prog); return }
        } catch (e) {}
    })

    // FORCE JUMP (priority 163): deep crouch then arms flung skyward
    var FJUMP_KF = [
        { t: 0.0, parts: { BODY: { x: 0 } } },
        { t: 0.3, parts: { BODY: { x: 22 }, RIGHT_LEG: { x: 40 }, LEFT_LEG: { x: 40 }, RIGHT_ARM: { x: 20 }, LEFT_ARM: { x: 20 } } },
        { t: 0.6, parts: { BODY: { x: -10 }, RIGHT_ARM: { x: -165, z: -8 }, LEFT_ARM: { x: -165, z: 8 }, RIGHT_LEG: { x: -10 }, LEFT_LEG: { x: -10 } } },
        { t: 1.0, parts: { BODY: { x: 0 }, RIGHT_ARM: { x: 0, z: 0 }, LEFT_ARM: { x: 0, z: 0 }, RIGHT_LEG: { x: 0 }, LEFT_LEG: { x: 0 } } }
    ]
    event.register('fs_fjump', 163, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var player = builder.getPlayer()
            var prog = fsProg(player, 'funstuff:fs_force', 'force_jump', 8)
            if (prog >= 0 && prog < 1.0) { fsApply(builder, FJUMP_KF, prog); return }
        } catch (e) {}
    })

    // SPEEDCUBING (priority 158): frantic finger-blur fiddling
    var CUBE_KF = [
        { t: 0.0, parts: { RIGHT_ARM: { x: 0 } } },
        { t: 0.15, parts: { RIGHT_ARM: { x: -62, y: -12 }, LEFT_ARM: { x: -58, y: 12 }, HEAD: { x: 18 } } },
        { t: 0.3, parts: { RIGHT_ARM: { x: -55, y: 10 }, LEFT_ARM: { x: -65, y: -10 }, HEAD: { x: 18 } } },
        { t: 0.45, parts: { RIGHT_ARM: { x: -65, y: -10 }, LEFT_ARM: { x: -55, y: 10 }, HEAD: { x: 18 } } },
        { t: 0.6, parts: { RIGHT_ARM: { x: -55, y: 12 }, LEFT_ARM: { x: -65, y: -12 }, HEAD: { x: 18 } } },
        { t: 0.8, parts: { RIGHT_ARM: { x: -60, y: 0 }, LEFT_ARM: { x: -60, y: 0 }, HEAD: { x: 12 } } },
        { t: 1.0, parts: { RIGHT_ARM: { x: 0, y: 0 }, LEFT_ARM: { x: 0, y: 0 }, HEAD: { x: 0 } } }
    ]
    var CUBE_MOVES = ['turn_u', 'turn_u_p', 'turn_r', 'turn_r_p', 'turn_f', 'turn_f_p',
                      'turn_d', 'turn_d_p', 'turn_l', 'turn_l_p', 'turn_b', 'turn_b_p']
    var CUBE_LIST = [['funstuff:fs_cube', 'scramble', 8], ['funstuff:fs_cube', 'inspect', 6]]
    for (var cm = 0; cm < CUBE_MOVES.length; cm++) CUBE_LIST.push(['funstuff:fs_cube', CUBE_MOVES[cm], 4])
    event.register('fs_cubing', 158, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var player = builder.getPlayer()
            for (var i = 0; i < CUBE_LIST.length; i++) {
                var prog = fsProg(player, CUBE_LIST[i][0], CUBE_LIST[i][1], CUBE_LIST[i][2])
                if (prog >= 0 && prog < 1.0) { fsApply(builder, CUBE_KF, prog); return }
            }
        } catch (e) {}
    })

    // BOOMBOX GROOVE (priority 158): shoulder bounce + head bob
    var GROOVE_KF = [
        { t: 0.0, parts: { HEAD: { x: 0 } } },
        { t: 0.2, parts: { HEAD: { x: 10 }, BODY: { z: 6 }, RIGHT_ARM: { z: 20 }, LEFT_ARM: { z: -12 } } },
        { t: 0.4, parts: { HEAD: { x: -6 }, BODY: { z: -6 }, RIGHT_ARM: { z: 12 }, LEFT_ARM: { z: -20 } } },
        { t: 0.6, parts: { HEAD: { x: 10 }, BODY: { z: 6 }, RIGHT_ARM: { z: 20 }, LEFT_ARM: { z: -12 } } },
        { t: 0.8, parts: { HEAD: { x: -6 }, BODY: { z: -6 }, RIGHT_ARM: { z: 12 }, LEFT_ARM: { z: -20 } } },
        { t: 1.0, parts: { HEAD: { x: 0 }, BODY: { z: 0 }, RIGHT_ARM: { z: 0 }, LEFT_ARM: { z: 0 } } }
    ]
    var GROOVE_LIST = [
        ['funstuff:fs_boombox', 'play_pigstep', 20],
        ['funstuff:fs_boombox', 'play_cat', 20],
        ['funstuff:fs_boombox', 'play_stal', 20]
    ]
    event.register('fs_groove', 158, (builder) => {
        try {
            if (!fsTpOnly(builder)) return
            var player = builder.getPlayer()
            for (var i = 0; i < GROOVE_LIST.length; i++) {
                var prog = fsProg(player, GROOVE_LIST[i][0], GROOVE_LIST[i][1], GROOVE_LIST[i][2])
                if (prog >= 0 && prog < 1.0) { fsApply(builder, GROOVE_KF, prog); return }
            }
        } catch (e) {}
    })
})

console.log('[Fun Stuff] client animation script loaded')