"""Ultra-simple 16-count Electric Slide retargeted for Microduck. The sequence follows the morphology-specific choreography supplied by the user: three travelling steps and a tap/bob in each direction, three steps back and a tap, then rock forward/back, step forward, and pivot left. The official 50 Hz walking policy remains active throughout; these functions only provide dance commands and small additive joint-space targets. The free root is never written after reset. """ from __future__ import annotations from dataclasses import dataclass import math import numpy as np from choreography import BEAT_SECONDS COUNTS_PER_WALL = 16 WALLS = 4 SHOW_BEATS = COUNTS_PER_WALL * WALLS SHOW_SECONDS = SHOW_BEATS * BEAT_SECONDS @dataclass(frozen=True) class SimpleCount: label: str swing: str placement: str weight_transfer: bool = True SIMPLE_COUNTS = ( SimpleCount("right step right", "right", "side_right"), SimpleCount("left follows right", "left", "follow_right"), SimpleCount("right step right", "right", "side_right"), SimpleCount("left tap + beak-snap head bob", "left", "touch_bob", False), SimpleCount("left step left", "left", "side_left"), SimpleCount("right follows left", "right", "follow_left"), SimpleCount("left step left", "left", "side_left"), SimpleCount("right tap + beak-snap head bob", "right", "touch_bob", False), SimpleCount("right step back", "right", "back"), SimpleCount("left step back", "left", "back"), SimpleCount("right step back", "right", "back"), SimpleCount("left tap", "left", "touch", False), SimpleCount("rock forward", "none", "lean_forward", False), SimpleCount("rock back", "none", "lean_back", False), SimpleCount("left step forward", "left", "forward"), SimpleCount("right scuff + pivot 90 degrees left", "right", "turn_scuff"), ) def _smoothstep(value: float) -> float: value = max(0.0, min(1.0, float(value))) return value * value * (3.0 - 2.0 * value) def count_phase(time_seconds: float) -> tuple[int, int, float]: beat = max(0.0, time_seconds) / BEAT_SECONDS whole = int(math.floor(beat)) return whole // COUNTS_PER_WALL, whole % COUNTS_PER_WALL, beat - whole def policy_for_time(time_seconds: float) -> str: """Keep a single continuous balance policy for the complete phrase.""" return "walk" def command_vector(time_seconds: float) -> np.ndarray: """Return the official walking policy's 13D command block.""" wall, count, phase = count_phase(time_seconds) spec = SIMPLE_COUNTS[count] command = np.zeros(13, dtype=np.float32) groove = math.sin(2.0 * math.pi * phase) pulse = math.sin(math.pi * phase) command[3] = 0.030 * groove command[4] = -0.025 * groove command[5] = 0.070 * groove command[6] = 0.055 * groove command[9] = -0.004 * abs(groove) command[10] = 0.020 * groove if spec.placement == "turn_scuff": command[2] = 4.9 elif spec.placement == "forward": command[0] = 0.32 elif spec.placement == "lean_forward": amount = _smoothstep(min(1.0, phase / 0.55)) * _smoothstep(min(1.0, (1.0 - phase) / 0.22)) command[11] += 0.075 * amount command[3] += 0.080 * amount command[4] -= 0.060 * amount command[9] -= 0.008 * amount elif spec.placement == "lean_back": amount = _smoothstep(min(1.0, phase / 0.55)) * _smoothstep(min(1.0, (1.0 - phase) / 0.22)) command[11] -= 0.070 * amount command[3] -= 0.060 * amount command[4] += 0.050 * amount if spec.placement == "touch_bob": snap = math.sin(math.pi * min(1.0, phase / 0.52)) ** 2 command[3] += 0.145 * snap command[4] -= 0.125 * snap command[9] -= 0.010 * snap if spec.weight_transfer and spec.placement != "turn_scuff": stance_side = 1.0 if spec.swing == "right" else -1.0 transition_scale = 0.80 if count == 0 and wall > 0 else 1.0 command[8] = 0.020 * stance_side * pulse * transition_scale command[10] += 0.045 * stance_side * pulse * transition_scale return command def joint_bias(time_seconds: float) -> np.ndarray: """Return a 14D morphology-aware additive joint target.""" wall, count, phase = count_phase(time_seconds) spec = SIMPLE_COUNTS[count] bias = np.zeros(14, dtype=np.float32) pulse = math.sin(math.pi * max(0.0, min(1.0, phase))) accent = math.sin(math.pi * _smoothstep(min(1.0, phase / 0.82))) transition_scale = 0.80 if count == 0 and wall > 0 else 1.0 p = pulse * transition_scale if spec.swing != "none" and spec.placement != "turn_scuff": lift = 0.30 if spec.placement in ("touch", "touch_bob") else 0.75 if spec.swing == "left": bias[3] += lift * pulse bias[4] += lift * 0.80 * pulse else: bias[12] -= lift * pulse bias[13] -= lift * 0.80 * pulse if spec.placement == "side_right": bias[10] -= 0.20 * p bias[9] -= 0.05 * p elif spec.placement == "follow_right": bias[0] -= 0.12 * p bias[1] -= 0.15 * p elif spec.placement == "side_left": bias[1] += 0.20 * p bias[0] += 0.05 * p elif spec.placement == "follow_left": bias[9] += 0.12 * p bias[10] += 0.15 * p elif spec.placement == "back": if spec.swing == "left": bias[0] -= 0.08 * p bias[2] += 0.30 * p else: bias[9] += 0.08 * p bias[11] -= 0.30 * p elif spec.placement == "forward": bias[0] += 0.05 * p bias[2] -= 0.24 * p elif spec.placement in ("touch", "touch_bob"): if spec.swing == "left": bias[2] += 0.10 * accent bias[3] += 0.10 * accent bias[4] += 0.08 * accent else: bias[11] -= 0.10 * accent bias[12] -= 0.10 * accent bias[13] -= 0.08 * accent elif spec.placement == "lean_forward": bias[2] -= 0.055 * pulse bias[11] += 0.055 * pulse bias[4] += 0.030 * pulse bias[13] -= 0.030 * pulse elif spec.placement == "lean_back": bias[2] += 0.050 * pulse bias[11] -= 0.050 * pulse bias[4] -= 0.025 * pulse bias[13] += 0.025 * pulse elif spec.placement == "turn_scuff": bias[12] -= 0.04 * pulse bias[13] -= 0.03 * pulse bias[7] += 0.22 * pulse return bias __all__ = [ "COUNTS_PER_WALL", "SHOW_BEATS", "SHOW_SECONDS", "SIMPLE_COUNTS", "SimpleCount", "command_vector", "count_phase", "joint_bias", "policy_for_time", ]