"""Tessel's first dance: 2 s loop at 120 BPM. Bounce on every beat; arms change shape each beat (right up, forward pump, left up, arms out). Writes dance.json for bridge/anim.py. Claude Opus 5.5, 2026-10-11.""" import json from pathlib import Path ARMS = [ # (left upper, left fore, right upper, right fore) per beat ([0.1, 0.9, -0.4], [0.3, 0.6, -0.7], [0.15, -0.35, 0.92], [0.1, -0.2, 0.97]), # right arm up ([0.6, 0.35, -0.7], [0.95, 0.0, 0.3], [0.6, -0.35, -0.7], [0.95, 0.0, 0.3]), # forward pump ([0.15, 0.35, 0.92], [0.1, 0.2, 0.97], [0.1, -0.9, -0.4], [0.3, -0.6, -0.7]), # left arm up ([0.0, 1.0, 0.15], [0.0, 0.3, 1.0], [0.0, -1.0, 0.15], [0.0, -0.3, 1.0]), # arms out, forearms up ] keys = [] for i in range(9): # eighth notes, 0..2.0 s, last = first for a clean loop t = round(i * 0.25, 2) beat = (i // 2) % 4 on = i % 2 == 0 lu, lf, ru, rf = ARMS[beat if on else beat] if not on: # halfway to the next shape n = ARMS[(beat + 1) % 4] mix = lambda a, b: [round((x + y) / 2, 3) for x, y in zip(a, b)] lu, lf, ru, rf = mix(lu, n[0]), mix(lf, n[1]), mix(ru, n[2]), mix(rf, n[3]) side = 1 if beat % 2 == 0 else -1 bend = 1.0 if on else 0.25 keys.append({ "t": t, "pos": {"mPelvis": [0, 0.035 * side, -0.07 * bend]}, "rot": {"mTorso": [3 * side, 0, 12 * side], "mHead": [0, 7 * bend, -6 * side], "mHipLeft": [0, -20 * bend, 0], "mKneeLeft": [0, 36 * bend, 0], "mAnkleLeft": [0, -16 * bend, 0], "mHipRight": [0, -20 * bend, 0], "mKneeRight": [0, 36 * bend, 0], "mAnkleRight": [0, -16 * bend, 0]}, "aim": {"mShoulderLeft": lu, "mElbowLeft": lf, "mShoulderRight": ru, "mElbowRight": rf}, }) keys[-1] = dict(keys[0], t=2.0) spec = {"name": "tessel dance", "duration": 2.0, "priority": 4, "loop": True, "loop_in": 0.0, "loop_out": 2.0, "ease_in": 0.4, "ease_out": 0.4, "hand_pose": "relaxed", "keys": keys} Path(__file__).with_name("dance.json").write_text("// generated by gen_dance.py\n" + json.dumps(spec, indent=1))