"""第138〜142話の冒頭の動き（2026-11-05）。話ごとに違う動き。
138 出ようとして、ガクッと止まる（2回）→ しょんぼり → 左の脇へスッと寄る（柱のわき）
139 走ってきて、見えない物に鼻先でぶつかりそうになり、のけぞって止まる →「！？」→ じりじり下がる
140 止まって「障害物？」→ じりじり横へ大回り（小刻みに）→「？」
141 その場でくるくる回る（横に縮んで裏返る）→ 目が回る →「次ノ階デス」
142 勢いよく走ってきて、見えない壁にゴツン → 跳ね返って揺れる →「…何モ？」"""
import sys
import numpy as np
import motion30
import hakobu_motion as M
from hakobu_motion import T, ease, _approach

class Stall138:
    dur = 3.8; empty = True
    def motion(self, t):
        for t0 in (0.5, 1.4):                      # 前へ出かけて、ガクッ
            if t0 <= t < t0 + 0.7:
                v = t - t0
                if v < 0.18: return dict(dx=-40 * ease(v / 0.18), rot=-2)
                k = np.exp(-(v - 0.18) * 10) * np.cos(2 * np.pi * 6 * (v - 0.18))
                return dict(dx=-40, rot=4 * k, sy=1 - 0.04 * k)
        if t < 1.4: return dict(dx=-40) if t >= 1.2 else {}
        if t < 2.6: return dict(dx=-40, dy=8 * ease(min(1, (t - 2.1) / 0.3)) if t > 2.1 else 0)
        v = t - 2.6
        return dict(dx=-40 - 300 * ease(min(1, v / 0.6)), dy=-4 * abs(np.sin(2 * np.pi * 3 * v)))
    def face(self, t):
        if t < 0.5: return T("案内", "待機中")
        if t < 2.1: return ("big", "big"), None, "o"
        if t < 2.6: return ("down", "down"), "trouble", "frown"
        return ("happy", "happy"), None, "closed"
    def ring(self, t): return 0.6
    def blur(self, t): return t >= 2.6
    def dim(self, t): return 1.0
    def cam(self, t): return (0, 0)

class Door139:
    dur = 3.6; empty = True
    def motion(self, t):
        if t < 0.8: return dict(dx=240 * (1 - ease(t / 0.8)), dy=-5 * abs(np.sin(2 * np.pi * 4 * t)))
        if t < 1.5:                                 # のけぞって止まる
            v = t - 0.8; k = np.exp(-v * 7) * np.cos(2 * np.pi * 4 * v)
            return dict(rot=7 * k, dx=12 * (1 - np.exp(-v * 9)))
        v = t - 1.5                                 # じりじり下がる
        return dict(dx=12 + 120 * ease(min(1, v / 1.6)) + 3 * np.sin(2 * np.pi * 5 * v), rot=0.8 * np.sin(2 * np.pi * 5 * v))
    def face(self, t):
        if t < 0.8: return ("open", "open"), None, "mid"
        if t < 1.5: return T("！？")
        return ("down", "down"), "trouble", "wave" if int(t * 8) % 2 else "wave2"
    def ring(self, t): return 0.6
    def blur(self, t): return t < 0.8
    def dim(self, t): return 1.0
    def cam(self, t): return (0, 0)

class Creep140:
    dur = 3.8; empty = True
    def motion(self, t):
        if t < 0.8: return _approach(t, 0.8, 200)
        if t < 1.5: return {}
        v = t - 1.5                                 # 小刻みに、じりじり大回り（左へ・少し傾く）
        steps = np.floor(v * 5) + ease((v * 5) % 1)
        x = -min(steps * 22, 230)
        return dict(dx=x, rot=-2.5 * min(1, v / 0.4), dy=-2 * abs(np.sin(2 * np.pi * 5 * v)))
    def face(self, t):
        if t < 0.8: return ("open", "open"), None, "mid"
        if t < 1.5: return T("障害物？")
        if t < 3.0: return ("dot", "dot"), "trouble", "wave" if int(t * 6) % 2 else "wave2"
        return T("？")
    def ring(self, t): return 0.6
    def blur(self, t): return t < 0.8
    def dim(self, t): return 1.0
    def cam(self, t): return (0, 0)

class Spin141:
    dur = 3.8; empty = True
    def motion(self, t):
        if t < 0.8: return _approach(t, 0.8, 230)
        if t < 2.4:                                 # その場でくるくる（横に縮んで裏返る）
            v = t - 0.8; ph = 2 * np.pi * 1.4 * v
            c = np.cos(ph)
            return dict(sx=max(0.08, abs(c)), flip=c < 0, dy=-3 * abs(np.sin(ph)))
        v = t - 2.4                                 # 目が回って、ふらふら
        return dict(rot=4 * np.sin(2 * np.pi * 1.6 * v) * np.exp(-v * 1.2), dx=6 * np.sin(2 * np.pi * 1.6 * v) * np.exp(-v * 1.2))
    def face(self, t):
        if t < 0.8: return ("open", "open"), None, "mid"
        if t < 2.4: return ("open", "open"), None, "closed"
        if t < 3.0: return ("x", "x"), None, "wave" if int(t * 8) % 2 else "wave2"
        return T("次ノ階", "デス")
    def ring(self, t): return 0.6
    def blur(self, t): return 0.8 <= t < 2.4
    def dim(self, t): return 1.0
    def cam(self, t): return (0, 0)

class Bonk142:
    dur = 3.6; empty = True
    HIT = 0.6
    def motion(self, t):
        if t < self.HIT: return dict(dx=330 * (1 - t / self.HIT), dy=-6 * abs(np.sin(2 * np.pi * 5 * t)), rot=-3, sx=1.05, sy=0.97)
        v = t - self.HIT                            # ゴツン：つぶれて跳ね返り、揺れる
        k = np.exp(-v * 5) * np.cos(2 * np.pi * 3.5 * v)
        return dict(dx=40 * (1 - np.exp(-v * 8)), sx=1 - 0.08 * k, sy=1 + 0.05 * k, rot=6 * k)
    def face(self, t):
        if t < self.HIT: return ("open", "open"), None, "mid"
        if t < 1.6: return ("x", "x"), None, "o"
        return T("…何モ？")
    def ring(self, t): return 1.0 if self.HIT <= t < 1.0 else 0.6
    def blur(self, t): return t < self.HIT
    def dim(self, t): return 1.0
    def cam(self, t):
        v = t - self.HIT
        return (int(10 * np.exp(-v * 9) * np.sin(2 * np.pi * 20 * v)), 0) if 0 <= v < 0.4 else (0, 0)

M.PATTERNS.update(ep138=Stall138, ep139=Door139, ep140=Creep140, ep141=Spin141, ep142=Bonk142)
if __name__ == "__main__":
    for arg in sys.argv[1:]:
        nm, _, kv = arg.partition(":")
        pat = M.PATTERNS[nm]()
        for item in filter(None, kv.split(",")):
            k, v = item.split("=", 1)
            if k in motion30.STR: setattr(pat, k, v)
            else: setattr(pat, k, float(v))
        M.render(pat, f"ハコブくん_{nm}.mp4")
