# 考察記事を YouTube の掛け合い動画にする ― 3本目は配膳ロボットの改造の回

- 記録: LOG432（ひとり研究所の裏側）
- 公開: 2026/11/2
- 記事のページ: https://pixelstomotors.com/record/deep-dive-video-serving-robot-modding
- サイト: Pixels to Motors（ADAPT DESIGN / 島津 栄一） https://pixelstomotors.com/
- 要約: 考察記事 LOG406 を YouTube の掛け合い動画（9分39秒）にした。別のエージェントの裏取りで言い過ぎと二次報道の数字を外し、作り手の答え5つを入れた。冒頭は50.9秒から43.1秒に縮め、ショートは入りの動きを回ごとに変えた。

> AI が読むための記事の全文です（コード・実行ログを含む）。引用するときは上の「記事のページ」の住所を使ってください。

---

考察記事 LOG406（配膳ロボットの改造の損得）を、YouTube の掛け合い動画「配膳ロボットの改造はどこまでOK？ 責任・契約・追加学習を考えた」（9分39秒）にした。台本は、書いたのとは別の AI エージェントが記事と出典に1文ずつ突き合わせ、言い過ぎ4か所と、二次の報道だけにあった「3週間近く」という数字を外してから声にした。

この回で良くなったこと・できるようになったことは、次の3つです。

- **動画の中で、確かめられていない話をしなくなった。** 突き合わせで、配膳ロボットの外部接続の穴の件は、見つけた人が「無視された」という主張をあとで取り下げていたと分かりました。もとの記事にも同じ言い方が残っていたので、記事 LOG406 には本文を直さずに追記を入れました
- **冒頭が目安の45秒に収まった。** 自己紹介が始まるまでが50.9秒あったのを、法律の2文を1文にまとめて43.1秒にしました。声はその1文だけ作り直し、ほかの98文はそのまま使いました
- **声と絵を、会話の中から作れるようになった。** 声は ElevenLabs の 99文（約3,300字）を、Mac の shell から4回に分けて作りました。合わせて3分ほどです。脱線の絵4枚は Gemini の画像の API で同時に作り、いちばん遅い1枚で31.1秒でした

前の回で同じ作業にかかった時間は測っていないので、どれだけ時間が浮いたかは数字で言えません。声を作り手が Mac で作る形だったころは、作り手の手が空くまで書き出しに進めませんでした。この回は、作り手が確認する所（読み・冒頭の長さ・サムネイル）でだけ止まりました。

いちばん大きな直しは、台本を固める前の裏取りでした。もとの記事は、作り手と Claude の対話で、法律・契約・メーカーの開き方を調べて考えた記録です。調べた範囲の理解を、動画では短く言い切ることになるので、記事より強い言い方になりやすい所でした。

| やったこと | なぜ | 結果 |
|---|---|---|
| 別のエージェントに、記事と出典に1文ずつ突き合わせさせた | 書いた本人では同じ見落としをするため | 重い指摘3つ（3週間・追加学習の判子・「答えがない」）を含む12か所を直した |
| 作り手に5つ質問し、答えを台本に入れた | 実感は Claude が作ると作り物に聞こえるため | 「全部知りたいから、できる限り分解する」など5つ |
| 声を ElevenLabs で作った | ナレーションは AI 音声の決まり | 99文（ハコブくん78・作り手21） |
| 脱線の絵を Gemini の API で4枚作った | 話の途中で画面を大きく変えるため | 自分で改造・ロボ検・扉、全開・課金で解放 |
| 書き出しを2回した | 冒頭の長さと「ロボ検」の読みの指摘 | 9分47秒 → 9分39秒。冒頭 50.9秒 → 43.1秒 |
| サムネイルを2案作った | YouTube で選ばれる1枚を決めるため | 案A「責任は改造した人」。真ん中の文字を大きくした |
| 縦長ショート3本を作った | 本編に人を連れてくる入口にするため | 17.6〜20.6秒。入りのハコブくんの動きを3本とも別にした |
| 記事 LOG406 に追記を入れた | 裏取りで分かったことを、記事を読む人にも届けるため | 本文は直さず、2か所に「追記（2026-11-02）」 |

下の図で、触り方の5段階と切れ目、天丼の3枚の申請書、裏取りで直した所を見られます。

[図解: LOG432_five_steps.html](https://pixelstomotors.com/embeds/LOG432_five_steps.html)

![考察回3本目の冒頭。紙の上に「改造申請書」（速さの上限を外す・荷物を倍積む・夜も走る）、右上に朱の判子「却下」。左下にハコブくん、右下に作り手のドット絵](https://pixelstomotors.com/media/LOG432_01_hook_kyakka.png)

YouTube の考察回は、ほかの回も同じ型で作っています。動画は YouTube のチャンネル https://www.youtube.com/@PixelstoMotors にあります。


## 作業ログ ― 台本の裏取りから、ショートを作り直すまで

ここから下は作業の記録です。効果は、測れた数字を「変わったこと」に書き、ほかは見込みです。視聴の数字は、YouTube に公開したあとにアナリティクスで確かめます。この記事を書いた時点で、動画はまだ公開していません。

### 考察回の3本目に選んだ記事：LOG406「配膳ロボットの次の改造は、追加学習になる」

考察回（壁打ちの型）は、案内役のロボット「ハコブくん」が Claude の役、作り手がその相手になって、考察記事の「考えている途中」を掛け合いで見せる YouTube の型です（LOG427 で作り、LOG429 で2本目を作った）。LOG429 で選んだ6本の台本のたたき（声を作る前の下書き）のうち、残っていた5本から、作り手が「どこまでいじっていいか。かな。」と選んだのがこの回です。

| 言葉 | この記事での意味 |
|---|---|
| 考察回 | 考察記事をもとにした掛け合いの動画。ハコブくん（まじめすぎるボケ）と作り手（ツッコミ兼本音） |
| 台本のたたき | 声を作る前の下書きの台本。作り手のセリフ・相づち・脱線の絵・表情の割り当てまで書く |
| 【本人】【答え】 | 台本の中の、作り手が記事の中で言った言葉と、動画のために聞いた答え。話し言葉に整えるが、中身は足さない |
| 【相づち】 | Claude が案を書き、作り手が「採っていい」と了承した短いツッコミ |
| 天丼 | 回の中で3回くり返す笑い。この回は、ハコブくんが「改造申請書」を出すたびに判子が落ちる |
| 脱線 | 紙（考察ノート）が飛んでいき、画面いっぱいの絵に替わって数秒で戻る場面 |

もとの記事の問いは「メーカーのロボットは、どこまで変えられるか」です。触り方を5段階（設定・公式の外部接続・外付け・ソフトの書き換え・機体の改造）に分け、3段目と4段目の間で、法律・保証・保守が分かれる、という整理でした。

![触り方の5段階の場面。下から「設定」「公式の外部接続」「外付け」は墨の面、「ソフトの書き換え」「機体の改造」は薄い灰の面で右へずれ、切れ目ができる。左上に「法律・保証・保守も、ほぼこの切れ目で分かれる」](https://pixelstomotors.com/media/LOG432_02_five_steps_cut.png)

### 裏取り：別のエージェントが記事と出典に1文ずつ突き合わせた

台本のたたきの「裏を取る所」7項目を、書いたのとは別の AI エージェントに渡し、記事と、ウェブの一次情報か信頼できる報道に当たってもらいました。台本は直さず、指摘の一覧だけを返す、という頼み方にしました。返ってきた重い指摘は3つです。

| 指摘 | 中身 | 台本の直し |
|---|---|---|
| 外部接続の穴の「3週間近く」 | 「3週間近く」はロシアのニュースサイトの二次の報道の言い方。見つけた人の報告では、知らせてから9日後に広く連絡し、すかいらーく・ゼンショーに知らせて48時間以内に返事が来ていた。しかも、メーカーが最初の知らせを受け取っていなかったと分かったとして、「無視された」を取り下げていた | 日数と相づち「3週間は長いね」を外した |
| 追加学習の判子 | 追加学習が長い目で見て主役になる、と見立てたのは Claude。作り手の言葉は「かなり魅力がありそう」という評価だった。判子「この見立ては、エイイチから」だと、考えの出どころが作り手に移る | 判子を「エイイチの推し」、縦書きを「追加学習の形に、魅力がある。」に |
| 最後の「法律も規格も答えを持っていない」 | EU の機械規則は、機械学習で振る舞いを変える安全の部品を、外部の審査が要る区分にすでに入れている | 「使う人があとから学ばせた動きを、誰が確かめるのかは、まだはっきりしていない」に絞った |

ほかに直したのは、製造物責任法の言い方（責任は加工した物を人に渡したときに生まれるので「仕事として加工して人に渡した業者」に）、車の機能を遠くから外した例（出どころは VICE ではなく Electrek。2020年で、騒ぎのあと戻された）、Model Y の約2,000ドル（出典に「もともと入っている性能を抑えている」とは書いていないので「部品を替えずに、ソフトの更新だけで速くなる」に）、Claude の見立てを事実のように言っていた2か所です。

- ねらい：調べた範囲の理解を、動画で短く言い切るときに、記事より強い言い方にしない
- 変わったこと：台本の12か所を直した。二次の報道だけにあった数字（3週間近く）が台本から消えた
- 見込める効果：動画を見た人が出典を開いたときに、話が合わない所がなくなる。確かめられるのは、公開後のコメントと問い合わせ

もとの記事 LOG406 にも「3週間近く無視され」と「法律も規格も、まだここには答えを持っていません」が残っていました。公開した LOG の本文は直さない決まりなので、作り手の指示で、2か所の後ろに「追記（2026-11-02）」として補足を足しました。

> 公開している記事 LOG406 も直さない。過去の記事を意味なく直さない（なぜならLOGを書く時点で未来のことはわからないため）。ただし追記する形で補足を入れるのはOK。

### 作り手の答え5つ：分解したい人、即答で払う人

実感は Claude が作ると作り物に聞こえるので、台本ごとに作り手に聞きます。この回は5つ聞き、答えを話の流れに合う所に置きました。

| 聞いたこと | 答え | 置いた所と、ハコブくんの返し |
|---|---|---|
| 店で見て、まず直したいと思った所は？ | 速さより、人とすれ違うときや、行き先や戻り方が分からなくなって迷う動き | 持ち込みの直後。「速さではなく、すれ違い方と、迷い方。覚えておきます。」あとで「値打ちはソフトの側」の所で回収し、最後の申請書も「病院の廊下で、人とすれ違う動きを追加学習する」にした |
| 車やバイクをいじったことは？ 車と比べたきっかけは？ | ある。ロボットも個人で持つようになったら、改造したい人は必ず多い。自分も改造したい | 車と比べる所の頭。「……改造したい側の人でしたか。」 |
| もともと入っている性能を有料で開ける形、払う？ | 払います | Model Y の話のあと。「即答でした。」 |
| 自分がメーカーなら、どこまで開ける？ | 全部知りたいので、できる限り分解する | 半分開いたロボットの話のあと。「……それは、開ける側ではなく、開けたい側の答えです。」 |
| 学ばせた動きが安全かは、誰が確かめるのがいい？ | エンジニアと店舗の社員・スタッフを交えてがベスト（実際にはコストの面で難しそう） | 残った問い。「いちばん良い形と、払える形が、まだ離れています。」 |

相づちの差し替え案「鍵は見るのに、誰の鍵かは見てないのか。」は、作り手に「なんのこと？」と聞かれました。合鍵が本物かは確かめていたが、その合鍵がその機体の持ち主のものかは確かめていなかった、という話を、たとえで言おうとして伝わらなかったものです。たとえをやめて「よその店のロボットまで、動かせたってこと？」にしました。

### 声：ElevenLabs の99文を、Mac の shell から4回に分けて作った

声は ElevenLabs の Eleven v4 で、ハコブくんの声と、作り手の文に使う常連の声（槙原）の2つです。鍵は Mac につないだ外付けの SSD（KIOXIA）にあり、Mac の shell から `tts_eleven.py` を動かすと、スクリプトが鍵を読みます。1回の実行が170秒を超えると止まるので、ハコブくんの78文は30文ずつ3回に分けました。

| 実行 | 文 | 文字数 | かかった時間 |
|---|---|---|---|
| エイイチ（槙原の声） | 21文 | 約690字 | 38.5秒 |
| ハコブ | 30文 | 約1,021字 | ― |
| ハコブ2 | 30文 | 約1,010字 | ― |
| ハコブ3 | 18文 | 約606字 | ― |
| 直し1（冒頭の法律の1文） | 1文 | 約45字 | ― |
| 試し_ロボ検（4通り） | 4文 | 約132字 | ― |
| 直し2（ロボ検の文） | 1文 | 約30字 | ― |

全文は、作り手が確かめる前にまとめて作りました。そのあと、読みが怪しい語（ロボ検・ユニツリー・四足・モデルワイ・二千ドル）の文番号を作り手に伝えて聞いてもらい、「ロボ検のイントネーションが変」と言われた1文だけを直しました。直し方は、同じ文を4通りの書き方で作って聞き比べてもらう形です。

| 試し | 書き方 | 結果 |
|---|---|---|
| 01 | 名前は、ロボケンです。 | 採用 |
| 02 | 名前は、ろぼけんです。 | ― |
| 03 | 名前は、ロボ・ケンです。 | ― |
| 04 | 名前は、ロボット車検、略して、ロボケンです。 | ― |

もとの読みの直しは「ロボけん」（漢字の検をひらがなに）でした。ElevenLabs はひらがなとカタカナで抑揚が変わることがあり、この語はカタカナがいちばん自然でした。字幕は「ロボ検」のままです。

### 冒頭を45秒以内に：法律の2文を1文にまとめた

チャンネルの決まりでは、冒頭（自己紹介が始まるまで）は45秒が目安です。確認用 v1 では50.9秒ありました。いちばん長かったのは、EU と日本の法律を1文ずつ言う所（7.5秒と6.7秒）です。2文を「EUでも日本でも、条件によっては、改造した人が、メーカーと同じ責任を負う側に入ります。」の1文（6.7秒）にまとめました。画面の紙には、EU と日本の2枚の札をそのまま残しています。

- ねらい：冒頭を目安の45秒に収める。声は作り直す文を1つにする
- 変わったこと：自己紹介の開始が 50.9秒 → 43.1秒。全体が 9分47秒 → 9分39秒
- 見込める効果：冒頭で離れる人が減る。確かめるのは、公開後の YouTube の「30秒時点の視聴者維持率」

消した文は、台本の中で空の文（字幕も声もなく、間が0秒）として残しました。文の番号が1つずれると、画面の場面・表情・効果音の割り当て（文の番号で書いている）を全部書き直すことになるためです。書き出しの部品は、中身のない文を1コマで飛ばします。

### 脱線の絵4枚：Gemini の画像の API で同時に作った

脱線の絵は、マンガ風2枚（ハコブくんが出る）と写真風2枚です。マンガ風は、4コマ『ハコブくん』の第105話のページと、ハコブくんの三面図（正面・横）を参照の画像に付けて、絵柄をそろえました。1枚ずつの依頼を、Mac の shell から4つ同時に投げています。

| 絵 | 種類 | かかった時間 | 画面に重ねた一言 |
|---|---|---|---|
| 自分で改造 | マンガ風。腕のないハコブくんが、工具をテープで頭に留め、背中のふたを開けて得意げ | 31.1秒 | 自分で改造。 |
| ロボ検 | マンガ風。車検場のラインに配膳ロボット・掃除ロボット・ハコブくんが並び、検査員が困っている | 29.6秒 | ロボ検、開幕。 |
| 扉全開 | 写真風。夕方の古い屋敷の扉・窓・門が全部開いている | 19.0秒 | 扉、全開。 |
| 課金で解放 | 写真風。鍵のかかった宝箱の横に硬貨の投入口があり、手が金貨を入れようとしている | 17.7秒 | 課金で解放。 |

自分で改造の絵には、黒白のマンガなのに緑のケーブルが1本入っていました。動画に使う前に、マンガ風の2枚は灰色にしました。

![脱線の絵4枚。左上がロボ検（検査ラインに3台と困った検査員）、右上が扉全開の屋敷、左下が自分で改造するハコブくん、右下が課金の宝箱](https://pixelstomotors.com/media/LOG432_04_dassen_four.jpg)

### 天丼：改造申請書と朱の判子（却下・却下・保留）

この回の天丼は、ハコブくんが「改造申請書」を出すたびに、朱の判子が落ちる形にしました。紙の上に書類の型（タイトル・申請者・チェックの付いた項目）を置き、判子は二重の枠の四角で、斜めに叩きつけます。

| 回 | 申請書の項目 | 判子 |
|---|---|---|
| 冒頭 | 速さの上限を外す・荷物を倍積む・夜も走る | 却下（作り手「それ、事故が起きたら、誰の責任？」） |
| やり取り2 | 外からつなげる口を、全部開ける・誰とでも仲良くする | 却下（すぐに「口を増やすほど、穴も増える」へ） |
| 最後 | 病院の廊下で、人とすれ違う動きを、追加学習する | 保留（作り手「……それは、ありかもね」） |

![最後の改造申請書。項目は「病院の廊下で、人とすれ違う動きを、追加学習する」、朱の判子は「保留」。下に作り手の「……それは、ありかもね」](https://pixelstomotors.com/media/LOG432_03_hold_stamp.png)

ハコブくんの体の動きは3回です。1回目の却下でのけぞり、ロボ検がボツになると前のめりにコケて、作り手の「追加学習の形に、魅力がある」で全身で飛び出します。

### 書き出し v1・v2

紙の場面は HTML の CSS アニメーションで描き、時刻を止めて1コマずつ撮ります。その上に、ハコブくん・作り手のドット絵・字幕を重ねます。場面の一覧の画像を一度見て、はみ出し（「ロボットは、サブスク」が1行に入らない、Unitree の行が紙からはみ出す、判子が次の場面に残る など）を直してから書き出しました。

| 版 | 長さ | 確認用の大きさ | 直したこと |
|---|---|---|---|
| v1 | 9分47秒（17,621コマ） | 24.4MB | ― |
| v2 | 9分39秒（17,392コマ） | 24.0MB | 冒頭の法律を1文に（50.9秒 → 43.1秒）・「ロボ検」をロボケンの読みに |

![本編の場面の一覧（v1）。冒頭の申請書から、触り方の5段階、車とサブスク、ロボ検、扉全開、8つの形、課金で解放、追加学習、半分開いたロボット、まとめの表、残った問い、最後の申請書まで](https://pixelstomotors.com/media/LOG432_07_scene_sheet.jpg)

### サムネイル：「責任は改造した人」と、真ん中の文字を大きく

サムネイルの背景は、Gemini で作った写真風の絵です。閉店後のファミレスで、背中のふたが開いた配膳ロボットに、外された南京錠が下がり、床にドライバーとスパナが落ちています。主役を右に寄せ、左を文字の場所にしました。

| 案 | 上の札 | 大きな文字 | ハコブくんの吹き出し |
|---|---|---|---|
| A（決めた案） | 配膳ロボットを改造したら | 責任は／改造した人 | 却下です…… |
| B | ロボット、買っても | ソフトは／借り物／だった | どこまで触れる？ |

作り手は案Aを選び、「真ん中のテキストもっと大きくして」と言いました。「責任は」を150px から200px、「改造した人」を170px から210px にしました（1920幅のとき）。「人」の字がロボットの左端に少しかかりますが、文字の縁取りで読めます。タイトルは3案から「配膳ロボットの改造はどこまでOK？ 責任・契約・追加学習を考えた」に決まりました。

![決めたサムネイル（案A）。左に「配膳ロボットを改造したら」の札と大きな「責任は／改造した人」、右に背中のふたが開いた配膳ロボットと南京錠、左下にハコブくんと吹き出し「却下です……」](https://pixelstomotors.com/media/LOG432_05_thumbnail_a.png)

### 縦長ショート3本：入りのハコブくんを、回ごとに違う大げさな動きに

ショートは、本編の文を4〜6つ選んで30秒以内にし、最初の数秒は脱線の絵を画面いっぱいに出します。版1を見た作り手から、入りの動きについて指示がありました。

> 動画の入りのハコブくんの動きをもっと大袈裟に動画毎にいろんな動きをさせてコミカルに！

版1では、ショート1・2の絵にハコブくんが描いてあるので、動くハコブくんを隠していました。版2では、背景の絵の位置をずらして絵のハコブくんを画面の外に出し、動くハコブくんを大きく見せることにしました。動きは書き出しの部品に3つの型を足し、3本で同じ型を使わないようにしています。

| ショート | 長さ | 入りの動き（版2） | 効果音 |
|---|---|---|---|
| 1 改造してください | 17.6秒 | spin：左から転がり込み、高く跳んで空中で1回転、どすんと着地してつぶれ、ぺこぺこおじぎ | しゅっ・上がる音・着地のぽん |
| 2 ロボ検 | 17.6秒 | dash：右から猛ダッシュ、行き過ぎて急ブレーキでぐらつき、汗をかいてガタガタ震える。「ロボ検」で笑顔 | しゅっ・ブレーキ |
| 3 課金で解放 | 20.6秒 | frenzy：下から飛び出し、お金の話に大興奮して、左右に大きく揺れながらぴょんぴょん跳ねる | 上がる音・ぽんの連打 |

- ねらい：ショートの最初の1〜2秒で、スワイプされずに止まってもらう
- 変わったこと：入りの動きが、3本とも同じ「下から出る・跳ねる」から、回ごとに違う型になった。ロボ検の回は、背景の位置をずらして検査員が画面に入った
- 見込める効果：スワイプされずに見られた割合が上がる。確かめるのは、公開後の YouTube の「スワイプされずに視聴された割合」

![縦長ショート3本の0.9秒の画面。上の段が版1（絵だけ、またはハコブくんが下から出る所）、下の段が版2（ハコブくんが空中で回る・ダッシュで傾く・大きく揺れる）](https://pixelstomotors.com/media/LOG432_06_shorts_v1_v2.jpg)

字幕は1行11字で折ります。本編の文のうち「決まった手続きがない分、改造した人が、安全を自分で説明することになります。」と「それ、ロボットでもやりそうだね。」は11字の区切りで折れなかったので、ショートだけ「｜」で区切った字幕を書きました（声はそのまま）。

### 記事 LOG406 への追記

裏取りで分かった2つを、LOG406 の本文の後ろに追記として足しました。本文の文は1字も変えていません。サイトをビルドして、公開されたページに2か所の「追記（2026-11-02）」が出ることを確かめました。

| 場所 | 追記の中身 |
|---|---|
| 運用のデメリット「外から触れる口を増やすほど穴も増えます」の下 | 見つけた人が「無視された」を取り下げたこと。「3週間近く」は二次の報道の言い方で、報告では9日後に広く連絡し、48時間以内に返事が来た流れ。出典 The Register |
| まとめの「法律も規格も、まだここには答えを持っていません」の後 | EU の機械規則は、学習で振る舞いを変える安全の部品を、外部の審査が要る区分にすでに入れていること。残っているのは、使う人があとから学ばせた動きを、誰が確かめるか |

### 保存した場所

| もの | 場所 |
|---|---|
| 作り方のファイル（台本・場面・ショート・サムネイルの設定・概要欄の下書き・投稿文） | iCloud `PixelstoMotors/デジタルツイン学習R2R2R/動画/どこまでいじっていい/` |
| 脱線の絵とプロンプト・作るスクリプト | 同じフォルダの `背景/`（サムネイルの背景は `サムネ素材/`） |
| 声（ElevenLabs の mp3） | 同じフォルダの `voice_eleven/`（まとめた写しは `_merge/`） |
| YouTube に上げる一式（本編 v2・サムネイル・タイトル・概要欄・投稿文） | KIOXIA `r2r2r-data/videos/どこまでいじっていい/アップロード用/` |
| 縦長ショート（版2）とタイトル・説明文 | KIOXIA `r2r2r-data/videos/どこまでいじっていい/ショート/`（版1は `_旧版_版1/`） |
| 確認用の動画（v1・v2） | KIOXIA `r2r2r-data/videos/どこまでいじっていい/` |
| 動画の決まり（10節に3本目で足したこと） | iCloud `動画/_制作ベース/video_rules.md` と、プロジェクトの `claude/video_rules.md` |


## 作業ログ ― この回で使った台本・コード・プロンプト・出力（全文）

ここから下は、この回で使ったファイルと出力を、そのまま載せています。

### 台本のたたき（考察回_台本たたき.md・裏取りと作り手の答えを入れた最終版）

```markdown
# 考察回「壁打ちの型」― 台本のたたき（LOG406）

> 【相づち】は、2026-11-02 にエイイチが「採っていい」と了承済み（6本とも）。

共通の決まりは _ネタ帳/考察回_壁打ちの型/考察回_壁打ちの型_台本たたき.md と video_rules.md 10節。

もとの記事: LOG406「配膳ロボットの次の改造は、追加学習になる ― メーカー製品を触る損得」
https://pixelstomotors.com/record/service-robot-modding
尺の目安: 約8分　／　柱: ①入れたらどうなる（入れたあと、どこまで自分の店に合わせられるか）
記事の性格: 実物のロボットを改造した記録ではなく、Claude と対話しながら法律・契約・メーカーの開放の度合いを調べて考えた記録。法律の話は調べた範囲の理解で、専門家の判断ではない（動画でも1回言い、紙の下に小さく出す）

エイイチのセリフの印: 【本人】＝ LOG406 の引用の行を話し言葉に整えたもの（行末に記事の見出しの番号）。【見立て帳】＝ physical-ai.md に残っている本人の見立て。【相づち】＝ Claude の案。本人が採るか選ぶ

## この回の問い（紙に縦書き）

メーカーのロボットは、どこまで変えられるか。

（記事の問いと同じ。「どこまで」を置く文字にする）

## 天丼（この回のくり返しの笑い）

ハコブくんが「改造申請書」を出すたびに、エイイチが「却下」の判子を押す。冒頭・やり取り2・最後の3回。最後だけ判子が「保留」に変わる。

## 脱線の場面の候補

| 場面 | 画面いっぱいに出すもの（Gemini） | ひと言 |
|---|---|---|
| 「では僕は、5段目まで一気に」 | マンガ: ハコブくんが自分の背中のふたを開け、スパナとドライバーを両手に持って、うれしそうに中をのぞいている。床にネジが散らばる | 自分で改造。 |
| 「ロボットにも車検を作りましょう」 | マンガ: 車検場のような検査のラインに、配膳ロボット・掃除ロボット・ハコブくんが一列に並び、検査員の人がバインダーを持っている | ロボ検、開幕。 |
| 外から触れる口の穴の話 | 写真: 古い屋敷の扉・窓・門が、全部開け放たれている夕方の風景（人はいない） | 扉、全開。 |
| 「もう入っている性能に、お金を払う」 | 写真: 鍵のかかった宝箱。鍵穴の横に硬貨の投入口が付いていて、手が硬貨を入れようとしている | 課金で解放。 |

- 絵に文字を描かせない。ひと言は動画の側で重ねる
- ハコブくんが出る絵（1・2）は、三面図（hakobu_front.png・hakobu_side.png）を参照画像に付け、左下のハコブくんを消す

## 表情の割り当て（主な場面）

| 場面 | ハコブくん | エイイチ |
|---|---|---|
| 改造申請書 →「却下」 | ＞▽＜（得意げ）→ 困り眉 | 半目でツッコむ |
| 「5段目まで一気に」→ 脱線 | ＞▽＜ → 驚き（線で止められる） | のけぞって驚く → 腕組み |
| 「ソフトは買っていない」 | 驚き | 首をかしげる |
| 「ロボ検」 | ＞▽＜ → ボツで点の目 | 吹き出して笑う |
| 「細かいのは記事に」→「もう書いてあります」 | ウインク | 半目でツッコむ |
| 追加学習の見立て → 判子 | 驚き・全身で飛び出す →「それです」 | ひらめく |
| 「安全は閉じ、動きは開く」 | 目を閉じる（考える）→ 小さな笑み | うなずく |
| 最後の申請書 →「保留」 | 涙 → ＞▽＜ | 指さす |

## 台本

### 0 つかみ（0:00〜0:35）

ハコブくん：エイイチ、お願いがあります。（紙の代わりに「改造申請書」が出る。項目: 速さの上限を外す・荷物を倍積む・夜も走る）
ハコブくん：僕を、改造してください。
エイイチ【相づち】：それ、事故が起きたら誰の責任？
ハコブくん：……場合によっては、改造した人だそうです。（「却下」の判子が落ちる）
ハコブくん：たとえば EU では、メーカーが想定していない改変で危険を増やした人は、メーカーと同じ義務を負います。日本でも、仕事として加工して人に渡した業者は、作った側と同じ責任を負う側に入ります。
ハコブくん（ボケ）：車なら、買えば好きに改造できますよね。
エイイチ【相づち】：車も、保安基準に合わない改造はだめだよ。
ハコブくん：……失礼しました。車にも決まりがあります。そしてロボットは、買っても思ったほど自由になりません。
（紙に問いの縦書き「メーカーのロボットは、どこまで変えられるか。」。「どこまで」が置かれる）
ハコブくん：今日は答えを伝える回ではありません。エイイチが考えている途中を、そのまま見せます。

### 1 持ち込み：5段階の線（0:35〜1:50）

ハコブくん：（自己紹介の定型文）
ハコブくん：きっかけは、エイイチのこんな疑問でした。
エイイチ【本人】：搬送や配膳、掃除のロボットの動きや操作性を良くしたいとき、メーカーの製品をソフトもハードも触ったら、何が良くて何がまずいのか。（見出し1）
ハコブくん：エイイチが店で見て、まず直したいと思ったのは、どこですか？
エイイチ【答え】：直したい所はいくつかあるけど、速さより、人とすれ違うときと、行き先や戻り方が分からなくなって迷う動きを、まず直したいと思った。（2026-11-02 の答え1）
ハコブくん：速さではなく、すれ違い方と、迷い方。覚えておきます。
ハコブくん：これは実物を改造した話ではありません。Claude と一緒に、決まりと契約と実例を調べて考えた話です。（紙の下に小さく「法律の話は調べた範囲の理解。専門家の判断ではない」）
ハコブくん：調べると、触り方は5段階に分かれました。（紙に階段の面が5つ。下から: 設定・公式の外部接続・外付け・ソフトの書き換え・機体の改造）
ハコブくん（ボケ）：では僕は、5段目まで一気に。
（脱線: 自分で改造。→ 紙が戻ると、3段目と4段目の間に面の切れ目が入る）
ハコブくん：……3段目と4段目の間に、切れ目がありました。
ハコブくん（へー）：3段目まではメーカーが想定した内側、4段目からは外側です。法律も保証も保守も、ほぼこの切れ目で分かれます。
エイイチ【相づち】：思ったより手前で切れてるね。
ハコブくん：はい。しかも動きや操作性を良くしたい、という用事の多くは、3段目までで届く、というのが Claude の分析です。

### 2 やり取り1：車とサブスク（1:50〜3:20）

ハコブくん：ここでエイイチが、車と比べ始めました。
エイイチ【答え】：車やバイクは、自分でもいじってきた。ロボットも個人で持つようになったら、改造したい人は必ず多いと思う。僕もしたい。（答え2）
ハコブくん：……改造したい側の人でしたか。
エイイチ【本人】：車は買う、ロボットはサブスク。サブスクだと、基本的には、メーカーが用意したもの以上の改造は認められないってことだよね？（見出し2）
エイイチ【本人】：買っちゃえば、いろいろ変えられそうだけど。ハードはともかく、ソフトはアップデートが多くて追いつかなそう。（見出し2）
ハコブくん：サブスクは、ほぼその通りです。機体はメーカーか販売会社の物なので、契約で縛られます。
ハコブくん：問題は、買った場合です。
ハコブくん（へー）：ソフトは、ふつう買っていません。使う権利を借りているだけです。2020年には、中古で売られた車の運転を助ける機能を、メーカーが遠くから外した例が報じられています。
エイイチ【相づち】：え、買ったのに？
ハコブくん：騒ぎになって、あとで戻されました。でも、体は自分の物、機能は借り物でした。
ハコブくん：もう1つ違いがあります。車には「ここまで変えたら、この検査を受ける」という受け皿の制度があります。屋内のロボットには、それがありません。
ハコブくん（ボケ）：では、ロボットにも車検を作りましょう。名前は、ロボ検です。
（脱線: ロボ検、開幕。）
エイイチ【相づち】：誰が検査するの、それ。
ハコブくん：……そこが、まだ誰もいません。（ボケのボードに「ボツ」）
ハコブくん：決まった手続きがない分、改造した人が、安全を自分で説明することになります。
ハコブくん：そしてエイイチの言う「アップデートに追いつかない」が、実はいちばん大事な所でした。配膳ロボットの値打ちは、地図や、よけ方や、遠くからの見守りといった、ソフトの側にあるからです。
ハコブくん：エイイチが直したいと言った、すれ違い方と迷い方も、ここです。

### 3 やり取り2：口を開けると、穴も開く（3:20〜4:20）

ハコブくん（ボケ）：では僕は、外からつなげる口を全部開けておきます。誰でも仲良くできるように。（改造申請書の2枚目。判子「却下」）
ハコブくん（へー）：口を増やすほど、穴も増えます。2025年、ある配膳ロボットの会社の外部接続は、鍵は求めるのに、その機体の持ち主かを確かめておらず、他人のロボットを動かせる状態だったと報告されています。
（脱線: 扉、全開。）
エイイチ【相づち・新案2】：よその店のロボットまで、動かせたってこと？
ハコブくん：はい。運ぶ物によっては、笑えません。
ハコブくん：なので、開けるのはメーカーが用意した窓口だけ。それでも、メーカーの更新で作り直しになることがあります。

### 4 山場：満足できない人が増えたら（4:20〜6:00）

ハコブくん：ここで、話がもう一歩先へ進みました。
エイイチ【本人】：ロボットがもっと一般的になったら、メーカーの標準の性能だけじゃ満足できない人も出てくると思う。（見出し3）
ハコブくん：満足できない理由で、行き先が分かれます。考えられる形は、8つありました。1つ目は、メーカーが性能を有料で開ける形で……2つ目は……
エイイチ【相づち】：細かいのは記事に書いといて。
ハコブくん：もう書いてあります。（画面の隅に記事のカード）
ハコブくん：1つだけ。車では、もう起きています。
ハコブくん（へー）：車の Model Y では、2025年に、部品を替えずにソフトの更新だけで速くなる、約2,000ドルの有料の更新が出ています。
（脱線: 課金で解放。）
ハコブくん：……同じ体のまま、お金で速くなるんです。
エイイチ【相づち】：それ、ロボットでもやりそうだね。
ハコブくん：エイイチなら、払いますか？
エイイチ【答え】：払います。（答え3）
ハコブくん：即答でした。
ハコブくん：Claude の見立てでは、動きを学習で作るロボットが広がれば、8つのうち、長い目で見た主役は、最後の1つです。
ハコブくん：自分の店のお手本を足して、動きを学ばせ直す。追加学習です。改造の中心が、ネジやコードから、データに移る、と見ています。
ハコブくん：まとめると、改造は、ネジから、お手本へ。（紙に縦書きの一文。判子は押さない）
エイイチ【本人】：その追加学習の形には、かなり魅力がありそう。（見出し4。元の言葉は「Hにはかなり魅力がありそう」。H＝追加学習で自分の店専用の動きをつくる形）
エイイチ【見立て帳】：追加学習で、自分の現場専用の動きを作る方向には、かなり魅力を感じてる。
（暗転。縦書きで一文「追加学習の形に、魅力がある。」。判子「エイイチの推し」。エイイチはひらめきの絵）
（ハコブくんが全身で飛び出る）
ハコブくん：……それです。

### 5 やり取り3：半分開いたロボット（6:00〜7:10）

エイイチ【本人】：オープン型のロボットも出てきそう。ある程度は閉じてるけど、市場が大きくなったら、差別化でそういうメーカーが出てきそう。競争が激しくなるかも。（見出し4）
ハコブくん：調べたら、もう出ていました。
ハコブくん（へー）：中国のヒューマノイドと四足ロボットのメーカー Unitree は、関節ごとの細かい動きまで開けています。でも人型の G1 の開発の手引きによると、機体の中の計算機は2つに分かれていて、1つはメーカーのサービス専用で、開発には使えません。
ハコブくん：Claude は、この作りを、安全の部分はメーカーが閉じて、何をどう動かすかの部分を開ける形、と読みました。
（紙に面が2つ。墨の面「安全は、閉じる」・薄い灰の面「動きは、開く」）
ハコブくん：スマホの iPhone と Android の関係に近い、というのが Claude の見立てです。
ハコブくん：エイイチがメーカーなら、どこまで開けますか？
エイイチ【答え】：全部知りたいから、できる限り分解する。（答え4）
ハコブくん：……それは、開ける側ではなく、開けたい側の答えです。（エイイチは吹き出して笑う）
エイイチ【見立て帳】：ロボットで利益が残るのは、作る側より、入れて回す側だと思う。
ハコブくん：Claude の見立てでは、機体は値下がりし、値打ちは学習させた動きと、現場に入れて回す所へ移っていくかもしれません。

### 6 まとめ（7:10〜7:40）

（散らばった面が飛んできて、3行の表に収まる）

| 改造の形 | 中心 | 責任を負うのは |
|---|---|---|
| これまで | 機体・ソフトの書き換え | 改造した人 |
| メーカー公認の範囲 | 設定・外部接続・認められた業者 | メーカーと業者で分ける |
| これから | 現場のお手本で追加学習 | まだ決まっていない |

ハコブくん：改造の行き先は、中身を書き換えることではなく、メーカーが開けた範囲で、自分の現場に合う動きを学ばせること。
ハコブくん：ちなみに、ボツになった3つは、全部僕が言ったことです。（「5段目まで一気に」「ロボ検」「口を全部開ける」に判子）

### 7 残った問い（7:40〜8:10）

ハコブくん：答えが出ていないのは、最後の行です。決まりは動き始めていますが、使う人があとから学ばせた動きを、誰が確かめるのかは、まだはっきりしていません。
エイイチ【答え】：エンジニアと、店の社員やスタッフが一緒に確かめるのがいちばん。ただ実際は、コストの面で難しそう。（答え5）
ハコブくん：いちばん良い形と、払える形が、まだ離れています。
ハコブくん：このノートの全文は、サイトの記事にあります。決まりの名前も、出どころも、ぜんぶそちらに。
ハコブくん：あなたの店のロボット、どこまで触っていいか、契約書に書いてありますか。
ハコブくん：……では最後に。（改造申請書の3枚目。項目: 病院の廊下で、人とすれ違う動きを追加学習する）
エイイチ【相づち】：……それは、ありかもね。（判子が「保留」で落ちる）
ハコブくん：保留、いただきました。

## 裏取りの結果（2026-11-02・別のエージェントが記事と出典に突き合わせ）

- 車の機能を外した例: 2020年2月の Electrek の報道（Tesla・2017年の Model S・中古で販売店に売ったあと Autopilot と FSD を外し、騒ぎのあと戻した）。VICE ではなかった。台本は「2020年」「あとで戻された」を足し、メーカー名は出さない
- 配膳ロボットの API の穴: 発見者の報告と The Register（2025-08-29）で確認。「3週間近く」は二次の報道（Anti-Malware）の言い方で、発見者は9月3日に「無視された」を取り下げている → 台本から日数と「3週間は長いね」を外した。記事 LOG406 の105行目にも同じ言い方がある（記事は直さない。補足は追記で入れる・エイイチ 2026-11-02）
- 約2,000ドル: Tesla Model Y（2025年・米国）。「もともと入っている性能を抑えている」とは出典に書いていない → 「部品を替えずにソフトの更新だけで」に言い換えた
- Unitree G1: 手引きに「PC1 は公式のサービス用で開発に使えない。開発は PC2」。確認済み（非公式の写しで読んだ）
- EU 機械規則: 第18条（改変した人が製造者とみなされる）・第3条16号・全面適用 2027-01-20。概要欄に条文を書くなら原文を一度見る
- 製造物責任法: 責任は「加工して引き渡した物」に生まれる → 「仕事として加工して人に渡した業者」に直した
- 7 の「法律も規格も答えがない」: EU 機械規則はすでに、機械学習で振る舞いを変える安全部品を扱っている → 「使う人があとから学ばせた動きを誰が確かめるかは、まだはっきりしない」に絞った。記事 LOG406 の266・268行目も同じ（記事は直さない。追記なら可）
- 山場の判子: 追加学習を「長期の主役」と見立てたのは Claude。エイイチの言葉は「魅力がありそう」→ 判子を「エイイチの推し」、縦書きを「追加学習の形に、魅力がある。」に。Claude の見立てには記事の条件（動きを学習で作るロボットが広がれば）を付けた

## 裏を取る所（台本を固める前に・旧メモ）

- 中古車の運転支援機能を遠隔で止めた例（VICE の記事）。動画では「報じられています」で言う。メーカー名を出すかは決める（台本では出していない）
- 2025年の配膳ロボットの外部接続の穴と「3週間近く」（Anti-Malware の報道。発見者の報告）。会社名を出さない書き方にした。「報告されています」を保つ。手口は台本・図・概要欄に入れない
- 約2,000ドルの有料の性能解放（Electrek・EVANNEX）。車の名前と、いつの価格かを確かめる
- Unitree の計算機が2つに分かれる話（Unitree の G1 開発の手引き）。G1 の話なので、言うなら「人型ロボット G1 では」と付ける
- 「3段目までで用事の多くは届く」は記事の分析（④⑤⑥の店の結果と合う）。言い切りに聞こえないか、codex で見る
- EU 機械規則（2027年1月20日から全面適用・改変した人が製造者とみなされる条件）は、台本では数字を言っていない。概要欄の「扱う範囲」に書くなら、原文（2023/1230 の第18条）で確かめる
- 「iPhone と Android」は Claude の見立てなので、そう言っている。エイイチの見立てにしない

## エイイチに聞きたいこと

1. 実際の店で配膳ロボットを見て、「ここを直したい」と思った所はありますか？（1 持ち込み。本人の疑問の直後に置く）
2. 車やバイクを自分でいじったことはありますか？ 車と比べたくなったきっかけは？（2 やり取り1。「買っちゃえば変えられそう」の前後）
3. もう入っている性能を有料で開ける形、自分なら払いますか？（4 山場。「課金で解放」の直後のエイイチの一言）
4. 自分がロボットのメーカーなら、どこまで開けますか？（5 やり取り3。オープン型の話のあと）
5. 学ばせた動きが安全かは、誰が確かめるのがいいと思いますか？（7 残った問い。答えがあれば、ハコブくんの問いのあとに本人の一言として入れる）

## 記事との突き合わせで直した所

- つかみ「改造した人、だそうです」: 一般化していた → 「場合によっては」にし、EU（メーカーが想定していない改変で危険を増やした場合）と日本の製造物責任法（仕事として加工した者）の条件の範囲で言う形に。メーカーの責任を問いにくくなる話は記事でも「分析」なので、台本では言わない
- つかみ「車なら、買えば改造できます」: 記事では「思いがち」な考えで、車も保安基準に合わない改造は禁止 → ハコブくんのボケにして、エイイチの相づちですぐ正す
- やり取り1の【本人】: 「できない」を元の言葉どおり「認められない」に戻した
- 山場「改造は、ネジから、お手本へ。」: Claude の見立てなのに判子を押していた → ハコブくんの言葉にして判子を外し、判子は見立て帳の本人の言葉（追加学習で現場専用の動き）に押す
- 山場の【本人】: 前半（追加学習で自分の店専用の動き）は記事の見出しの言葉だった → 元の言葉「Hにはかなり魅力がありそう」を話し言葉に整え、中身の説明は【見立て帳】の行に分けた
- やり取り3 Unitree: 「人型ロボットのメーカー」→「ヒューマノイドと四足ロボットのメーカー」。計算機が2つに分かれる話は、G1 の開発の手引きによると言う
- やり取り3「機体は安くなり、値打ちが残るのは…」: Claude の見立てを言い切っていた → 「Claude の見立てでは…かもしれません」
```

### 裏取りの報告（別のエージェントが記事と出典に突き合わせた結果）

```markdown
台本のたたきを記事 LOG406 と1文ずつ照らし、「裏を取る所」7項目を一次情報で確かめました。台本は直していません。重い指摘は3つで、配膳ロボット Pudu の件の「3週間近く」、山場で Claude の見立てを事実やエイイチの見立てにしている所、最後の「法律も規格も答えがない」です。

## 場面ごとの指摘

### 0 つかみ
- **「仕事として加工した人は、責任を負う側に入ります」**
  - 問題：第2条3項の言い方としては正しいです。ただ、責任は加工した物を人に渡したときに生まれます（第3条「その引き渡したものの欠陥」）。自分の店のロボットを自分で改造して使うだけなら、製造物責任法は直接は関わりません（一般の不法行為の扱いになると思われます。調べた範囲での理解）。つかみでは「事故が起きたら改造した人」と続くので、店の人が自分で改造する話にまで広げて聞こえます。
  - 根拠：記事91行目、[製造物責任法 第2条・第3条](https://hourei.net/law/406AC0000000085)
  - 直し方の案：「日本でも、仕事として加工して人に渡した業者は、作った側と同じ責任を負う側に入ります」
- **「メーカーが想定していない改変で危険を増やした人は、メーカーと同じ義務」**
  - 問題：記事どおりです。正確には、定義（第3条16号）には「簡単な覆い程度では済まない、新たな保護の手当てが要るほどの」という条件も付きます。数字や条文番号を言わないなら、このままで許容範囲です。
  - 根拠：[item24 の解説](https://blog.item24.com/en/machine-and-equipment-construction/machinery-regulation-when-a-modification-project-creates-a-new-machine/)
- **「車も、保安基準に合わない改造はだめだよ」**
  - 確認済みです（道路運送車両法 第99条の2「何人も」）。[関東運輸局の資料](https://wwwtb.mlit.go.jp/kanto/jidou_gian/hoan/date/gyousei_jyouhou/2019/2/1.pdf)
  - 付記：記事136行目の罰則「6か月以下の懲役」は、2025年6月の刑法改正で「拘禁刑」に変わっています。台本では言っていないので、概要欄に書くときだけ気をつけてください。

### 1 持ち込み
- **【本人】の行**
  - 問題：元の言葉は「調査、分析してください」という頼みで、「搬送ロボット」と「操作性」も入っています。意味は大きくずれていません。
  - 直し方の案：「搬送や配膳、掃除のロボットの動きや操作性を良くしたいとき」に寄せる。
- **「動きや操作性を良くしたい用事の多くは、3段目までで届くそうです」**
  - 問題：記事では「分析」の節にある Claude の見立てで、シミュレーションの店での結果を根拠にしています。「そうです」だと、外から聞いた事実のように聞こえます。
  - 根拠：記事110行目
  - 直し方の案：「…届く、というのが Claude の分析です。シミュレーションの店でも、詰まりの多くは店の形と回し方を直して減りました」

### 2 やり取り1
- **【本人】の2行**：確認済みです（記事117〜118行目）。元の言葉の「外装」が抜けていますが、問題ありません。
- **「中古で買われた車の運転を助ける機能を、メーカーが更新のときに遠くから止めた例」**
  - 中身：2020年2月の報道です。メーカーは Tesla、車は2017年の Model S。Tesla が競売で販売店に売ったあと、遠隔の点検で「代金を受け取っていない」として、1月の更新で Autopilot（運転支援）と FSD（完全自動運転オプション）を外しました。報道が大きくなったあと、元に戻されています。
  - 問題：VICE の記事は2020年11月のもので、改造（jailbreak）の話です。止めた件のもとは Electrek です。また「戻された」ことを言わずに「買ったのは、体だけでした」で締めると、言い過ぎ気味です。
  - 根拠：[Electrek 2020-02-07](https://electrek.co/2020/02/07/tesla-takes-away-autopilot-used-car-sold-dealer/amp/)、[TNW 2020-02-13（機能が戻った件）](https://thenextweb.com/shift/2020/02/13/tesla-autopilot-surreptitiously-taken-now-given-back-alec-model-s/)
  - 直し方の案：出典を Electrek にします。「2020年に報じられています（その後、騒ぎになって戻されました）」と添えるか、オチを「体は自分の物、機能は借り物でした」に弱めます。
- **「屋内のロボットには受け皿の制度がない」「改造した人が安全を自分で説明する」「値打ちはソフトの側」**：記事と一致しています（138、146行目）。

### 3 やり取り2（重い指摘）
- **「鍵は求めるのに、持ち主かを確かめておらず、他人のロボットを動かせた」**
  - 確認済みです。発見者の報告に「有効なトークンかは確かめるが、権限は確かめない」「持ち主かを確かめない」とあります。[The Register 2025-08-29](https://www.theregister.com/2025/08/29/pudu_robots_hackable/)
- **「見つけた人は、3週間近く返事をもらえなかった」「3週間は長いね」**
  - 問題1（日数）：発見者が知らせたのは8月12日です。8月21日（9日後）に50人以上に送っても返事がなく、そのあとすかいらーく・ゼンショーに知らせ、48時間以内に返事が来ています。「3週間近く」はロシアのニュースサイト Anti-Malware という二次の報道の言い方です。
  - 問題2（撤回）：発見者は9月3日の追記で、Pudu は最初のメールを受け取っていなかったと分かったとして、「無視された」という主張を取り下げています。記事105行目も同じ誤りを含んでいます。
  - 直し方の案：日数とエイイチの相づちを外し、「知らせてから、すぐには届かなかったそうです」程度にします。あるいは、穴の話だけにして返事の話は削ります。
- **「メーカーの更新で作り直しになることがある」**：記事と一致しています（104行目、Bear の互換性の件）。

### 4 山場（重い指摘）
- **「約2,000ドルの有料の更新で開ける例」**
  - 確認済みです。車は Tesla の Model Y の新型（Long Range AWD）で、2025年に米国2,000ドル・カナダ2,700カナダドル。2020年の旧型 Model Y にも同じ2,000ドルの更新がありました。
  - 注意：EVANNEX は「もともと入っている性能をソフトで抑えている」とは明記していません（後輪駆動の型を外す理由から、そう読めるだけです）。
  - 根拠：[EVANNEX 2025-06-02](https://evannex.com/blogs/news/tesla-offers-acceleration-boost-upgrade-for-the-new-model-y)、[InsideEVs（2020年）](https://insideevs.com/news/445892/tesla-model-y-acceleration-boost)
  - 直し方の案：「車の Model Y では、2025年に約2,000ドル」と、車の名前と年を付けます。
- **「長い目で見て主役になりそうなのは最後の1つ」「改造の中心が、ネジやコードから、データに移ります」**
  - 問題：Claude の見立てを事実として言い切っています。記事には「動きを学習で作るロボットが広がると」という条件が付いていますが、台本では抜けています（202〜203行目）。また記事では、業務用で先に育つのはメーカー公認の調整業者（D）とアプリの市場（C）とされていて、その話も落ちています（211行目）。
  - 直し方の案：「Claude の見立てでは、動きを学習で作るロボットが広がれば、長い目で見た主役は追加学習。中心がデータに移る、と見ています」
- **判子「この見立ては、エイイチから」と、縦書き「追加学習で、自分の現場専用の動きを作る」**
  - 問題：H（追加学習）の形を出して「長期の主役」と見立てたのは Claude です。エイイチの言葉は「魅力がありそう」という評価です（222行目）。見立て帳にも「かなり魅力を感じている」と残っています。今の判子と縦書きでは、考え自体をエイイチが出したことになります。
  - 直し方の案：判子を「エイイチの推し」「魅力あり」などにし、縦書きを「追加学習の形に、魅力がある。」にします。
- **【本人】「その追加学習の形には、かなり魅力がありそう」**：確認済みです（222行目）。

### 5 やり取り3
- **【本人】「…そういうメーカーが出てくる」**
  - 問題：元は「出てきそう」で、「出てくる」は言い切りが強くなっています（222行目）。
  - 直し方の案：「出てきそう」に戻す。
- **Unitree G1 の計算機が2つに分かれる話**
  - 確認済みです。手引きに「PC1 は公式のサービスを動かし、開発には使えない。開発は PC2」とあります。[Unitree の G1 開発の手引き](https://support.unitree.com/home/en/G1_developer/quick_development)
  - 注意：中身は非公式の写しで読みました（公式のページはこの環境から開けませんでした）。
- **「転ばない、止まる、速さの上限。安全の部分はメーカーが閉じる」**
  - 問題：記事では Claude の解釈で、「業務用のオープン型もこの形になると見ます」と結ばれています（235行目）。一方で Unitree は関節ごとの制御まで開けているので、「速さの上限を閉じている」とまでは手引きで確かめられていません。
  - 直し方の案：「Claude は、この作りを『安全は閉じ、動きは開く』と読みました」
- **見立て帳「利益が残るのは…入れて回す側」**：確認済みです（physical-ai.md の 2026-10-29 の記録）。
- **「機体は値下がりし…かもしれません」**：確認済みです（243行目、Claude の見立てとして言えています）。

### 6 まとめ
- 確認済みです（記事262〜266行目と一致）。

### 7 残った問い（重い指摘）
- **「法律も規格も、まだここには答えを持っていません」**
  - 問題：記事268行目どおりですが、外の事実としては言い過ぎです。EU 機械規則は、機械学習で「自分で振る舞いを変える」安全部品を、外部の機関の審査が要る区分（附属書I）に入れています。附属書IIIにも、自律して振る舞いを変える機械の要件があると思われます（中身の原文は未確認）。
  - 根拠：[Taylor Wessing（第25条2項・附属書I Part A）](https://www.taylorwessing.com/en/insights-and-events/insights/2023/06/neue-maschinenverordnung)、[Baker McKenzie の解説](https://www.globalcompliancenews.com/wp-content/uploads/sites/43/2024/12/Baker-McKenzie-Machinery-Regulation.pdf)
  - 直し方の案：「決まりは動き始めていますが、使う人があとから学ばせた動きを誰が確かめるのかは、まだはっきりしていません」。

## 「裏を取る所」の残り
- **EU 機械規則**：確認済みです。改変した人が製造者とみなされるのは第18条、「実質的な改変」の定義は第3条16号、全面適用は2027年1月20日（第54条。一部の条は先に適用）。
  - 根拠：[EUR-Lex 2023/1230](https://eur-lex.europa.eu/eli/reg/2023/1230/oj)、[Taylor Wessing](https://www.taylorwessing.com/en/insights-and-events/insights/2023/06/neue-maschinenverordnung)
  - 注意：EUR-Lex の原文はこの環境から開けず、解説記事で確かめました。概要欄に条文番号を書く前に、原文を一度見てください。
- **法律の説明の強さ（全体）**：「調べた範囲の理解」の範囲に収まっていないのは、0の製造物責任法と7の「答えがない」の2か所です。ほかは記事の条件どおりに言えています。

## 言い直しが必要な重い指摘
1. **3 の「3週間近く返事がなかった」**：日数は二次の報道の言い方で、発見者本人の報告では9日＋その後の48時間です。しかも発見者は9月3日に「無視された」を取り下げています。日数と相づちを外す必要があります。
2. **4 の追加学習の扱い**：Claude の見立てを、条件（動きを学習で作るロボットが広がれば）を外して事実として言っています。さらに判子「この見立ては、エイイチから」で考えの出どころがエイイチに移っています。エイイチの言葉は「魅力がある」という評価なので、判子と縦書きもそれに合わせます。
3. **7 の「法律も規格も、まだ答えを持っていない」**：EU 機械規則はすでに、機械学習で振る舞いを変える安全部品を扱っています。「使う人があとから学ばせた動きを誰が確かめるかは、まだはっきりしない」に絞ってください。
```

### narration.py（本編の台本の1文ずつと、読みの直し・作り手の声で読む文）

```python
# -*- coding: utf-8 -*-
# 台本「メーカーのロボットは、どこまで変えられるか」— 考察回「壁打ちの型」の3本目（もとは LOG406）
#   2026-11-02 1版。もとの台本: 同じフォルダの 考察回_台本たたき.md（別のエージェントが記事と出典に突き合わせて直した版）
#   書き写した元: 動画/現場で扱えるか/narration.py（形・読みの直し・自己紹介とアウトロの定型・EI_LINES）
#   相づちはエイイチが「採っていい」と了承（2026-11-02）。port_ei は差し替えの新案（「よその店のロボットまで…」）
#   エイイチのセリフ: LOG406 の本人の言葉を話し言葉に整えたもの＋本人に聞いた答え5つ（bring_ask_ei・car_ei・unlock_ask_ei・maker_ei・rest_ei）
#     ＋見立て帳2つ（learn_ei は記事の「Hにはかなり魅力がありそう」と見立て帳を1文にまとめた・profit_ei）
#   出典（台本の中の事実）:
#     - EU 機械規則 2023/1230 第18条（改変した人が製造者とみなされる）・第3条16号: https://eur-lex.europa.eu/eli/reg/2023/1230/oj
#     - 製造物責任法 第2条3項・第3条（業として加工して引き渡した者）
#     - 道路運送車両法 第99条の2（保安基準に適合しない改造の禁止）
#     - 中古車の運転支援を外した例（2020年・Tesla Model S。あとで戻された）: Electrek 2020-02-07 https://electrek.co/2020/02/07/tesla-takes-away-autopilot-used-car-sold-dealer/
#     - 配膳ロボットの外部接続の穴（2025年）: The Register 2025-08-29 https://www.theregister.com/2025/08/29/pudu_robots_hackable/ （会社名は出さない。手口は言わない）
#     - Model Y の約2,000ドルの加速の更新（2025年・米国）: EVANNEX 2025-06-02 https://evannex.com/blogs/news/tesla-offers-acceleration-boost-upgrade-for-the-new-model-y
#     - Unitree G1 の計算機が2つに分かれる: Unitree G1 開発の手引き https://support.unitree.com/home/en/G1_developer/quick_development
# 話すのはハコブくん。エイイチの文は EI_LINES（槙原の声）
#
# 各要素: (場面の名前, 表情, 字幕の文, 後ろの間[秒])  間を省略すると 0.35 秒
FACES = ("base", "smile", "surprise", "doubt", "think", "trouble", "joy", "wink")

SEGS = [
 # 0 つかみ（改造申請書1枚目 →「却下」・問いの縦書き）
 ("hook_ask", "joy", "エイイチ、お願いがあります。", 0.4),
 ("hook_form", "joy", "僕を、改造してください。", 0.8),
 ("hook_ei", "base", "それ、事故が起きたら、誰の責任？", 0.6),
 ("hook_reject", "trouble", "……場合によっては、改造した人だそうです。", 0.8),
 ("hook_law", "base", "EUでも日本でも、条件によっては、改造した人が、メーカーと同じ責任を負う側に入ります。", 0.6),
 ("hook_law", "base", "", 0.0),   # 2026-11-02 冒頭を45秒以内にするため、法律の2文を上の1文にまとめた（文の番号をずらさないよう空けておく）
 ("hook_car", "joy", "車なら、買えば好きに改造できますよね。", 0.5),
 ("hook_car_ei", "base", "車も、保安基準に合わない改造はだめだよ。", 0.6),
 ("hook_car2", "trouble", "……失礼しました。車にも決まりがあります。｜そしてロボットは、買っても、思ったほど自由になりません。", 1.0),
 ("hook_q", "base", "今日は、答えを伝える回ではありません。エイイチが考えている途中を、そのまま見せます。", 1.8),
 # 1 自己紹介
 ("title", "smile", "Pixels to Motors。案内役のハコブくんです。"),
 ("title", "base", "この番組では、フィジカルAIのニュースや実機の記録から、現場で使える形を考えます。", 1.2),
 ("title_eiichi", "base", "エイイチは、本業のデザイナーをしながら、ロボティクスの研究と開発をしています。｜その中で分かったことと、そこから考えた見立てを、ハコブくんがお伝えします。", 1.2),
 ("title_kabe", "smile", "今回は、エイイチの壁打ちに、ハコブくんが付き合います。", 1.8),
 # 2 持ち込み：5段階の線（脱線①「自分で改造。」）
 ("bring", "base", "きっかけは、こんな疑問でした。", 0.4),
 ("bring_ei", "base", "搬送や配膳、掃除のロボットの動きや操作性を良くしたいとき、メーカーの製品を、ソフトもハードも触ったら、何が良くて、何がまずいのか。", 0.6),
 ("bring", "base", "これは、実物を改造した話ではありません。Claudeと一緒に、決まりと契約と実例を調べて考えた話です。", 0.6),
 ("bring_ask", "base", "店で見て、まず直したいと思ったのは、どこですか。", 0.4),
 ("bring_ask_ei", "base", "直したい所はいくつかあるけど、速さより、人とすれ違うときと、行き先や戻り方が分からなくなって迷う動きを、まず直したいと思った。", 0.6),
 ("bring_ask", "think", "速さではなく、すれ違い方と、迷い方。覚えておきます。", 1.0),
 ("steps", "base", "調べると、触り方は、5段階に分かれました。", 0.4),
 ("steps", "base", "設定を変える。メーカーが用意した外部接続を使う。外付けの物を足す。ソフトを書き換える。機体を改造する。", 0.6),
 ("steps_boke", "joy", "では僕は、5段目まで、一気に。", 1.5),
 ("steps_cut", "surprise", "……3段目と4段目の間に、切れ目がありました。", 0.5),
 ("steps_cut", "base", "3段目までは、メーカーが想定した内側。4段目からは、外側です。｜法律も保証も保守も、ほぼこの切れ目で分かれます。", 0.6),
 ("steps_cut_ei", "base", "思ったより、手前で切れてるね。", 0.5),
 ("steps_cut", "base", "はい。しかも、動きや操作性を良くしたい、という用事の多くは、3段目までで届く、というのがClaudeの分析です。", 1.8),
 # 3 やり取り1：車とサブスク（脱線②「ロボ検、開幕。」）
 ("car", "base", "ここで、話が車に移りました。", 0.4),
 ("car_ei", "base", "車やバイクは、自分でもいじってきた。ロボットも個人で持つようになったら、改造したい人は、必ず多いと思う。僕もしたい。", 0.6),
 ("car_reply", "surprise", "……改造したい側の人でしたか。", 0.8),
 ("car_ei2", "base", "車は買う、ロボットはサブスク。サブスクだと、基本的には、メーカーが用意したもの以上の改造は、認められないってことだよね？", 0.3),
 ("car_ei2", "base", "買っちゃえば、いろいろ変えられそうだけど。ハードはともかく、ソフトはアップデートが多くて、追いつかなそう。", 0.6),
 ("sub", "base", "サブスクは、ほぼその通りです。機体はメーカーか販売会社の物なので、契約で縛られます。", 0.4),
 ("sub", "base", "問題は、買った場合です。", 0.5),
 ("soft", "surprise", "ソフトは、ふつう買っていません。使う権利を借りているだけです。", 0.5),
 ("soft", "base", "2020年には、中古で売られた車の運転を助ける機能を、メーカーが遠くから外した例が、報じられています。", 0.5),
 ("soft_ei", "base", "え、買ったのに？", 0.5),
 ("soft", "trouble", "騒ぎになって、あとで戻されました。｜でも、体は自分の物、機能は借り物でした。", 1.0),
 ("rule", "base", "もう1つ違いがあります。車には、ここまで変えたら、この検査を受ける、という受け皿の制度があります。屋内のロボットには、それがありません。", 0.6),
 ("robokan", "joy", "では、ロボットにも車検を作りましょう。名前は、ロボ検です。", 1.5),
 ("robokan_ei", "base", "誰が検査するの、それ。", 0.5),
 ("robokan_botsu", "trouble", "……そこが、まだ誰もいません。", 1.0),
 ("rule", "base", "決まった手続きがない分、改造した人が、安全を自分で説明することになります。", 0.6),
 ("value", "base", "そして、アップデートに追いつかない、という所が、実は一番大事な所でした。"),
 ("value", "base", "配膳ロボットの値打ちは、地図や、よけ方や、遠くからの見守りといった、ソフトの側にあるからです。", 0.4),
 ("value", "smile", "最初に直したいと言っていた、すれ違い方と迷い方も、ここです。", 1.8),
 # 4 やり取り2：口を開けると、穴も開く（申請書2枚目 →「却下」・脱線③「扉、全開。」）
 ("port_boke", "joy", "では僕は、外からつなげる口を、全部開けておきます。誰とでも仲良くできるように。", 1.2),
 ("port", "base", "口を増やすほど、穴も増えます。"),
 ("port", "base", "2025年、ある配膳ロボットの会社の外部接続は、鍵は求めるのに、その機体の持ち主かを確かめておらず、他人のロボットを動かせる状態だったと、報告されています。", 1.2),
 ("port_ei", "base", "よその店のロボットまで、動かせたってこと？", 0.5),
 ("port", "trouble", "はい。運ぶ物によっては、笑えません。", 0.6),
 ("port", "base", "なので、開けるのは、メーカーが用意した窓口だけ。それでも、メーカーの更新で、作り直しになることがあります。", 1.8),
 # 5 山場：満足できない人が増えたら（脱線④「課金で解放。」・判子「エイイチの推し」・全身で飛び出す）
 ("peak", "base", "ここで、話がもう一歩、先へ進みました。", 0.4),
 ("peak_ei", "base", "ロボットがもっと一般的になったら、メーカーの標準の性能だけじゃ、満足できない人も出てくると思う。", 0.6),
 ("peak8", "base", "満足できない理由で、行き先が分かれます。考えられる形は、8つありました。｜1つ目は、メーカーが性能を有料で開ける形で……2つ目は……", 0.3),
 ("peak8_ei", "base", "細かいのは、記事に書いといて。", 0.4),
 ("peak8", "wink", "もう書いてあります。", 0.8),
 ("unlock", "base", "1つだけ。車では、もう起きています。", 0.4),
 ("unlock", "base", "車のModel Yでは、2025年に、部品を替えずに、ソフトの更新だけで速くなる、約2,000ドルの有料の更新が出ています。", 1.2),
 ("unlock", "surprise", "……同じ体のまま、お金で速くなるんです。", 0.5),
 ("unlock_ei", "base", "それ、ロボットでもやりそうだね。", 0.4),
 ("unlock_ask", "base", "払いますか。", 0.3),
 ("unlock_ask_ei", "base", "払います。", 0.5),
 ("unlock_ask", "wink", "即答でした。", 1.2),
 ("learn", "base", "Claudeの見立てでは、動きを学習で作るロボットが広がれば、8つのうち、長い目で見た主役は、最後の1つです。", 0.4),
 ("learn", "base", "自分の店のお手本を足して、動きを学ばせ直す。追加学習です。｜改造の中心が、ネジやコードから、データに移る、と見ています。", 0.6),
 ("learn", "think", "まとめると、改造は、ネジから、お手本へ。", 0.8),
 ("learn_ei", "base", "追加学習で、自分の現場専用の動きを作る形には、かなり魅力がありそう。", 1.0),
 ("learn_jump", "surprise", "……それです。", 1.8),
 # 6 やり取り3：半分開いたロボット
 ("open_ei", "base", "オープン型のロボットも出てきそう。ある程度は閉じてるけど、市場が大きくなったら、差別化で、そういうメーカーが出てきそう。競争が激しくなるかも。", 0.6),
 ("open", "base", "調べたら、もう出ていました。", 0.4),
 ("open", "base", "中国のヒューマノイドと四足ロボットのメーカー、Unitreeは、関節ごとの細かい動きまで開けています。"),
 ("open", "surprise", "でも、人型のG1の開発の手引きによると、機体の中の計算機は2つに分かれていて、｜1つはメーカーのサービス専用で、開発には使えません。", 0.6),
 ("open", "think", "Claudeは、この作りを、安全の部分はメーカーが閉じて、何をどう動かすかの部分を開ける形、と読みました。", 0.4),
 ("open", "base", "スマホのiPhoneとAndroidの関係に近い、というのがClaudeの見立てです。", 0.6),
 ("maker", "base", "エイイチがメーカーなら、どこまで開けますか。", 0.3),
 ("maker_ei", "base", "全部知りたいから、できる限り分解する。", 0.6),
 ("maker", "doubt", "……それは、開ける側ではなく、開けたい側の答えです。", 1.0),
 ("profit_ei", "base", "ロボットで利益が残るのは、作る側より、入れて回す側だと思う。", 0.5),
 ("profit", "base", "Claudeの見立てでは、機体は値下がりし、値打ちは、学習させた動きと、現場に入れて回す所へ、移っていくかもしれません。", 1.8),
 # 7 まとめ（散らばった面が3行の表に収まる）
 ("sum", "smile", "ここまでを、1枚にまとめます。", 0.5),
 ("sum", "base", "これまでの改造は、機体やソフトの書き換え。責任を負うのは、改造した人です。"),
 ("sum", "base", "メーカー公認の範囲なら、設定や外部接続、認められた業者。責任は、メーカーと業者で分けます。"),
 ("sum", "base", "これからは、現場のお手本で追加学習。責任を負うのは、まだ決まっていません。", 0.6),
 ("sum", "base", "改造の行き先は、中身を書き換えることではなく、メーカーが開けた範囲で、自分の現場に合う動きを学ばせること。", 0.8),
 ("sum", "wink", "ちなみに、ボツになった3つは、全部、僕が言ったことです。", 1.8),
 # 8 残った問い（申請書3枚目 →「保留」）
 ("rest", "base", "答えが出ていないのは、最後の行です。"),
 ("rest", "base", "決まりは動き始めていますが、使う人があとから学ばせた動きを、誰が確かめるのかは、まだはっきりしていません。", 0.5),
 ("rest", "base", "誰が確かめるのが、いいと思いますか。", 0.3),
 ("rest_ei", "base", "エンジニアと、店の社員やスタッフが、一緒に確かめるのがいちばん。ただ実際は、コストの面で難しそう。", 0.6),
 ("rest", "think", "いちばん良い形と、払える形が、まだ離れています。", 0.8),
 ("rest", "base", "この考察ノートの全文は、サイトの記事にあります。決まりの名前も、出どころも、ぜんぶそちらに。", 0.8),
 ("ask", "base", "あなたの店のロボット、どこまで触っていいか、契約書に書いてありますか。", 1.5),
 ("end", "joy", "……では最後に。病院の廊下で、人とすれ違う動きを、追加学習させてください。", 0.6),
 ("end_ei", "base", "……それは、ありかもね。", 0.6),
 ("end", "joy", "保留、いただきました。", 1.8),
 # 9 アウトロ
 ("outro", "smile", "この動画が役に立ったら、高評価とチャンネル登録をお願いします。"),
 ("outro", "base", "ロボットの開発や導入の相談は、概要欄の連絡先から、エイイチに直接どうぞ。"),
 ("outro", "wink", "コメントは、エイイチが全部読んでいます。それではまた。", 1.8),
]

# エイイチの声で読む文（槙原の声を流用・video_rules.md 1-4）。場面の名前が _ei か _ei2 で終わる文
EI_LINES = [i for i, seg in enumerate(SEGS) if seg[0].endswith(("_ei", "_ei2"))]

# 「エイイチ」を残す文（自己紹介・アウトロのほかに名前が要る文）。文の頭で探す（video_rules.md 3-1）
NAME_OK = [i + 1 for i, seg in enumerate(SEGS) if seg[2].startswith((
    "エイイチ、お願いが", "今日は、答えを伝える回では", "今回は、エイイチの壁打ちに", "エイイチがメーカーなら"))]

READ = [
 ("Pixels to Motors", "ピクセルズ トゥー モーターズ"), ("Claude", "クロード"), ("EU", "イーユー"),
 ("Model Y", "モデルワイ"), ("Unitree", "ユニツリー"), ("G1", "ジーワン"), ("iPhone", "アイフォーン"), ("Android", "アンドロイド"),
 ("フィジカルAI", "フィジカルエーアイ"), ("AI", "エーアイ"), ("2,000ドル", "にせんドル"), ("2025年", "にせんにじゅうごねん"),
 ("2020年", "にせんにじゅうねん"), ("5段階", "ごだんかい"), ("5段目", "ごだんめ"), ("3段目", "さんだんめ"), ("4段目", "よんだんめ"),
 ("8つ", "やっつ"), ("3つ", "みっつ"), ("2つ", "ふたつ"), ("1つ目", "ひとつめ"), ("2つ目", "ふたつめ"), ("1つ", "ひとつ"), ("1枚", "いちまい"),
 ("栄一", "えいいち"), ("「", ""), ("」", ""), ("｜", ""), ("…", ""),
]

ELEVEN_READ = [
    ('エイイチ', '栄一'),   # video_rules.md 3-1
    ('Claude', 'クロード'),
    ('EU', 'イーユー'),
    ('Model Y', 'モデルワイ'),
    ('Unitree', 'ユニツリー'),
    ('G1', 'ジーワン'),
    ('2,000ドル', '二千ドル'),
    ('一番', 'いちばん'),
    # この回で先回りして足す読み（全文を聞いて確かめる）
    ('5段階', '五段階'),
    ('5段目', '五段目'),
    ('3段目', '三段目'),
    ('4段目', '四段目'),
    ('8つ', '八つ'),
    ('ロボ検', 'ロボケン'),   # 2026-11-02 エイイチが試し_01 に決めた（ロボけんは抑揚が変）
    ('四足', 'よんそく'),
    ('入れて回す', 'いれてまわす'),
    ('外付け', 'そとづけ'),
    ('借り物', 'かりもの'),
    ('入れないで', 'いれないで'),
]

OVERRIDE = {}

def reading(t):
    t = OVERRIDE.get(t, t)
    for a, b in READ:
        t = t.replace(a, b)
    return t


def parse(seg):
    """1行を (場面, 表情, 字幕, 間) にそろえる"""
    if len(seg) >= 3 and seg[1] in FACES:
        scene, face, text = seg[0], seg[1], seg[2]
        pause = seg[3] if len(seg) > 3 else 0.35
    else:
        scene, face, text = seg[0], "base", seg[1]
        pause = seg[2] if len(seg) > 2 else 0.35
    return scene, face, text, pause
```

### scenes_kabe.py（本編の紙の場面・申請書と判子・脱線・表情と動き・効果音）

```python
# -*- coding: utf-8 -*-
# 動画「メーカーのロボットは、どこまで変えられるか」— 考察回「壁打ちの型」の3本目（もとは LOG406）の場面
# 書き写した元: 現場で扱えるか/scenes_kabe.py（型の決まりは video_rules.md 10節）
#   紙の文字は真ん中にそろえる／線を使わず面で分ける（事実＝墨・エイイチの見立て＝濃い灰・ハコブくん＝薄い灰）
#   朱は判子とリアクション（却下・保留・ボツ・即答・エイイチの推し）だけ／字幕は動かさない
#   脱線（画面いっぱいの絵）は4回: 自分で改造（マンガ）・ロボ検（マンガ）・扉、全開（写真風）・課金で解放（写真風）。どれも Gemini で作った絵
#   天丼: ハコブくんが「改造申請書」を出すたびに、判子が落ちる。冒頭「却下」・やり取り2「却下」・最後だけ「保留」
import os
from kabe_kit import *
import scene_kit as _sk

_tl = Timeline()
tl = _tl
N = len(_tl.idx)
A = lambda i, off=0.0: tl.start(i) + off          # 動画の頭からの秒
S = tl.speech                                      # その文を読み終わるまでの長さ
LOGO = _sk.LOGO
BRAND = f'<img src="{LOGO}" style="position:absolute;right:56px;top:40px;height:30px;filter:invert(1);opacity:.55">'
SANS = "font-family:'Noto Sans CJK JP',sans-serif;"
BG = "src/bg/"
GEM = "絵は生成AI（Gemini）で作成"
LAW = "法律の話は、調べた範囲の理解です。専門家の判断ではありません"

BLOCKS = []


def block(name, sents, fn, video=None):
    b0 = _tl.start(sents[0])
    T = lambda i, off=0.0: tl.start(i) - b0 + off
    E = lambda i: tl.end(i) - b0
    b = dict(name=name, sents=list(sents))
    if video:
        b["video"] = video
    else:
        b["html"] = page(fn(T, E) + BRAND)
    BLOCKS.append(b)


def inkl(text, t, step=0.05):
    """「／」で行を分けて書く（1〜2文字だけ次の行に落ちないように）"""
    out, tt = [], t
    for k, part in enumerate(text.split("／")):
        out.append(ink(part, tt, step)); tt += len(part) * step
    return "<br>".join(out)


def line(text, t, size=30, weight=700, step=0.05, style=""):
    return f'<div style="{SANS}font-size:{size}px;font-weight:{weight};{style}">{ink(text, t, step)}</div>'


def qline(text, t, size=46, step=0.06, style=""):
    return f'<div class="serif" style="font-size:{size}px;font-weight:700;line-height:1.5;{style}">{ink(text, t, step)}</div>'


def src(text, t=0.0):
    return f'<div class="src" style="opacity:0;animation:show .01s linear {t:.3f}s forwards">{text}</div>'


def bigline(text, t, size=110, style=""):
    return f'<div class="serif" style="font-size:{size}px;font-weight:900;line-height:1.15;{style}">{land(text, t)}</div>'


def hanko(text, t, style, size=72, t_hide=None):
    """朱の判子（却下・保留）。四角い枠で、斜めに落ちる。t_hide で消す"""
    an = f"animation:slam .35s cubic-bezier(.5,0,.75,0) {t:.3f}s forwards" + (f", hide .2s linear {t_hide:.3f}s forwards" if t_hide else "") + ";"
    return (f'<div class="stamp" style="{an}font-family:\'Noto Serif CJK JP\',serif;font-size:{size}px;'
            f'padding:6px 26px;border:8px double #f5f3ee;outline:6px solid #c4472a;{style}">{text}</div>')


FORM_CSS = """<style>
.form { position:relative; width:760px; background:#fffdf8; border:3px solid #22201c; padding:26px 40px 30px; font-family:'Noto Sans CJK JP',sans-serif; color:#22201c; text-align:left; }
.form h3 { font-family:'Noto Serif CJK JP',serif; font-size:50px; font-weight:900; text-align:center; letter-spacing:.2em; margin-bottom:6px; }
.form .who { font-size:24px; color:#5a564f; text-align:center; margin-bottom:18px; }
.form .it { display:flex; align-items:center; gap:18px; font-size:36px; font-weight:700; padding:8px 0; border-top:2px solid #e2ddd2; opacity:0; animation:pop .4s ease-out forwards; }
.form .it b { display:inline-flex; width:40px; height:40px; border:4px solid #22201c; align-items:center; justify-content:center; font-size:30px; }
</style>"""


def form(no, items, t_in, t_items):
    """改造申請書（天丼）。items は文字のリスト、t_items はそれぞれが出る秒"""
    its = "".join(f'<div class="it" style="animation-delay:{t:.3f}s"><b>✓</b>{txt}</div>' for txt, t in zip(items, t_items))
    return (FORM_CSS + f'<div class="form" style="opacity:0;animation:pop .45s ease-out {t_in:.3f}s forwards">'
            f'<h3>改造申請書</h3><div class="who">No.{no}　申請者：ハコブくん</div>{its}</div>')


# ================= 0 つかみ（改造申請書1枚目 →「却下」） =================
def hook(T, E):
    return (paper(tag("考察ノート") + '<div class="center" style="gap:30px">'
                  + form(1, ["速さの上限を外す", "荷物を倍積む", "夜も走る"], T(0, 0.3), [T(1, 0.2), T(1, 0.6), T(1, 1.0)])
                  + card("mi", inkl("それ、事故が起きたら、誰の責任？", T(2, 0.3), 0.06), T(2, 0.1), "エイイチ", w=700, anim="wipe")
                  + '</div>', 0.0)
            + hanko("却下", T(3, 0.25), "left:1110px;top:150px", size=80)
            + shake_js([(T(3, 0.6), 0.3)]))
block("hook", [0, 1, 2, 3], hook)


def hook_law(T, E):
    return (paper(tag("改造した人は、どうなるか") + '<div class="stack" style="top:76px;gap:16px">'
                  + card("fact", inkl("メーカーが想定していない改変で、危険を増やした人は、／メーカーと同じ義務を負う", T(4, 0.6), 0.04), T(4, 0.3), "EU", w=900)
                  + card("fact", inkl("仕事として加工して人に渡した業者は、／作った側と同じ責任を負う側に入る", T(4, 2.8), 0.04), T(4, 2.5), "日本", w=900)
                  + '<div class="row2" style="gap:24px;align-items:center;margin-top:6px">'
                  + card("hb", "車なら、買えば好きに改造できる", T(6, 0.3), "ハコブくん", w=470, fade=T(7, 1.0), style="font-size:30px")
                  + card("mi", inkl("車も、保安基準に／合わない改造はだめ", T(7, 0.3), 0.06), T(7, 0.1), "エイイチ", w=400, anim="wipe", style="font-size:30px")
                  + '</div>'
                  + f'<div class="serif" style="font-size:38px;font-weight:700">{ink("ロボットは、買っても、", T(8, 2.6), 0.06)}'
                    f'{land("思ったほど自由じゃない", T(8, 3.8), "font-size:48px;font-weight:900")}</div>'
                  + '</div>' + src(LAW, T(4, 0.3)), 0.0))
block("hook_law", [4, 5, 6, 7, 8], hook_law)


def hook_q(T, E):
    return paper(tag("考察回　壁打ち") +
                 '<div class="center"><p class="v serif" style="font-size:58px;line-height:1.6;font-weight:300;letter-spacing:.06em;">'
                 f'<span data-ink="{T(9, 0.3):.2f},0.09" style="font-weight:700;">メーカーの</span><br>'
                 f'<span data-ink="{T(9, 0.9):.2f},0.09">ロボットは、</span><br>'
                 f'{land("どこまで", T(9, 1.8), "font-size:108px;font-weight:900")}<br>'
                 f'<span data-ink="{T(9, 2.6):.2f},0.09">変えられるか。</span></p></div>', 0.0)
block("hook_q", [9], hook_q)

# ================= 1 自己紹介（DAY124 の映像とテロップ） =================
O_TITLE = '<div class="lt"><img class="a" src="' + LOGO + '"><div class="b">フィジカルAI解説</div><div class="c">案内役：ハコブくん（AI音声）</div></div>'
O_EIICHI = '<div class="lt"><img class="a" src="' + LOGO + '"><div class="b">エイイチ</div><div class="c">デザイナー／ロボティクスの研究・開発</div></div>'
O_OUTRO = '<div class="lt"><img class="a" src="' + LOGO + '"><div class="b">相談は概要欄の連絡先から</div><div class="c">高評価・チャンネル登録／コメントはエイイチが全部読んでいます</div></div>'
OVERLAYS = {"title": _sk.overlay(O_TITLE), "title_eiichi": _sk.overlay(O_EIICHI), "outro": _sk.overlay(O_OUTRO)}
_t_title = tl.end(11) - tl.start(10)
block("title", [10, 11], None, video=("src/day124_crop.mp4", 4.5, "png/ovl/title.png"))
block("title_eiichi", [12], None, video=("src/day124_crop.mp4", 4.5 + _t_title, "png/ovl/title_eiichi.png"))


def title_kabe(T, E):
    return paper(tag("考察回") + '<div class="center" style="gap:30px">'
                 + f'<div class="serif" style="font-size:150px;font-weight:900;line-height:1.1">{land("壁打ち", T(13, 0.5))}</div>'
                 + line("エイイチの考えに、ハコブくんが付き合う", T(13, 1.3), size=34, weight=500)
                 + '</div>', 0.0)
block("title_kabe", [13], title_kabe)

# ================= 2 持ち込み：5段階の線（脱線①「自分で改造。」） =================
def bring(T, E):
    return (paper(tag("持ち込み") + '<div class="stack" style="top:96px;gap:24px">'
                  + card("mi", inkl("搬送や配膳、掃除のロボットの動きを良くしたいとき、／メーカーの製品を、ソフトもハードも触ったら？", T(15, 0.4), 0.04), T(15, 0.15), "エイイチの疑問", w=920, anim="wipe")
                  + line("実物の改造ではなく、Claude と一緒に、決まり・契約・実例を調べた話", T(16, 1.6), size=28, weight=500, style="color:#5a564f")
                  + card("hb", "店で見て、まず直したいのは？", T(17, 0.2), "ハコブくん", w=520, style="font-size:30px")
                  + card("mi", inkl("速さより、人とすれ違うときと、／迷う動きを、まず直したい", T(18, 0.4), 0.05), T(18, 0.15), "エイイチ", w=760, anim="wipe")
                  + '</div>' + src(LAW, T(16, 0.3)), 0.0)
            + stamp("覚えた", T(19, 2.4), "left:1250px;top:430px;font-size:48px"))
block("bring", [14, 15, 16, 17, 18, 19], bring)


STEP_CSS = """<style>
.st { position:absolute; display:flex; align-items:center; justify-content:center; font-family:'Noto Sans CJK JP',sans-serif; font-weight:700; font-size:30px;
      opacity:0; animation:pop .4s ease-out forwards; }
.st i { font-style:normal; font-size:22px; margin-right:12px; opacity:.7; }
@keyframes split { to { transform: translateX(var(--gx)); } }
.zone { position:absolute; font-family:'Noto Sans CJK JP',sans-serif; font-weight:900; font-size:30px; letter-spacing:.1em; opacity:0; animation:show .3s linear forwards; }
</style>"""


def steps(T, E):
    fly = (T(22, S(22) - 0.2), E(22) + 0.05)
    t_cut = T(23, 0.3)
    labels = ["設定", "公式の外部接続", "外付け", "ソフトの書き換え", "機体の改造"]
    # 階段の面: 下から上へ。左下から右上に積む。4段目から右へずれて切れ目ができる
    html = ""
    for k, lab in enumerate(labels):
        x, y, w, h = 60 + k * 150, 600 - k * 96, 300, 84
        col = ("#22201c", "#f5f3ee") if k < 3 else ("#dcd6c9", "#22201c")
        gx = "70px" if k >= 3 else "0px"
        appear = T(20, 1.2 + 0.25 * k) if k < 1 else T(21, [0.0, 1.0, 3.3, 4.8, 6.6][k])
        html += (f'<div class="st" style="left:{x}px;top:{y}px;width:{w}px;height:{h}px;background:{col[0]};color:{col[1]};--gx:{gx};'
                 f'animation:pop .4s ease-out {appear:.3f}s forwards, split .6s cubic-bezier(.3,1.4,.4,1) {t_cut:.3f}s forwards"><i>{k + 1}</i>{lab}</div>')
    zones = (f'<div class="zone" style="left:60px;top:700px;animation-delay:{T(24, 0.4):.3f}s">メーカーの想定の内側</div>'
             f'<div class="zone" style="right:40px;top:160px;color:#5a564f;animation-delay:{T(24, 2.2):.3f}s">外側</div>')
    return (dassen(BG + "自分で改造.jpg", fly[0] + 0.05, fly[1] - 0.05, words="自分で|!改造。", word_style="top:70px;font-size:130px", src_note=GEM)
            + STEP_CSS + paper(tag("触り方の5段階") + html + zones
                               + f'<div style="position:absolute;left:40px;top:110px;width:420px;animation:hide .3s linear {T(24, 3.6):.3f}s forwards">'
                               + card("hb", "5段目まで、一気に", T(22, 0.3), "ハコブくん", w=380, botsu=E(22) + 0.15, fade=T(23, 0.2), style="font-size:30px") + '</div>'
                               + f'<div style="position:absolute;left:60px;top:40px;width:540px;opacity:0;animation:show .3s linear {T(24, 4.0):.3f}s forwards">'
                               + f'<div class="serif" style="font-size:34px;font-weight:900;line-height:1.5;text-align:left">法律・保証・保守も、<br>ほぼこの切れ目で分かれる</div></div>', 0.0, flights=[fly])
            + shake_js([(t_cut, 0.3)]))
block("steps", [20, 21, 22, 23, 24], steps)


def steps2(T, E):
    return paper(tag("切れ目は、思ったより手前") + '<div class="center" style="gap:34px">'
                 + card("mi", inkl("思ったより、手前で切れてる", T(25, 0.3), 0.06), T(25, 0.1), "エイイチ", w=600, anim="wipe")
                 + f'<div class="serif" style="font-size:42px;font-weight:700;line-height:1.5">{ink("動きや操作性を良くしたい用事の多くは、", T(26, 1.2), 0.05)}<br>'
                   f'{land("3段目まで", T(26, 4.2), "font-size:96px;font-weight:900")}{ink("で届く", T(26, 4.8), 0.08)}</div>'
                 + line("Claude の分析（シミュレーションの店でも、詰まりの多くは店の形と回し方の直しで減った）", T(26, 6.0), size=24, weight=500, style="color:#5a564f")
                 + '</div>', 0.0)
block("steps2", [25, 26], steps2)

# ================= 3 やり取り1：車とサブスク（脱線②「ロボ検、開幕。」） =================
def car(T, E):
    return paper(tag("やり取り 1 ／ 車と比べると") + '<div class="center" style="gap:30px">'
                 + card("mi", inkl("車やバイクは、自分でもいじってきた。／ロボットも個人で持つようになったら、／改造したい人は必ず多い。僕もしたい", T(28, 0.4), 0.045), T(28, 0.15), "エイイチ", w=880, anim="wipe")
                 + bigline("改造したい側の人", T(29, 0.4), size=80)
                 + '</div>', 0.0)
block("car", [27, 28, 29], car)


def car2(T, E):
    col = lambda kind, head, sub, t: (f'<div class="card k-{kind}" style="width:420px;opacity:0;animation:pop .45s ease-out {t:.3f}s forwards">'
                                      f'<span class="serif" style="font-size:40px;font-weight:900;line-height:1.3;white-space:nowrap">{head}</span><br><span style="font-size:26px">{sub}</span></div>')
    return paper(tag("買う？　借りる？") + '<div class="stack" style="top:100px;gap:26px">'
                 + '<div class="row2" style="gap:34px">'
                 + col("hb", "車は、買う", "買えば、いろいろ変えられそう", T(30, 0.3))
                 + col("fact", "ロボットは、サブスク", "用意された以上は、認められない？", T(30, 1.6))
                 + '</div>'
                 + card("mi", inkl("ハードはともかく、ソフトは／アップデートが多くて、追いつかなそう", T(31, 0.4), 0.05), T(31, 0.15), "エイイチ", w=780, anim="wipe")
                 + card("fact", "サブスク：機体はメーカーか販売会社の物。契約で縛られる", T(32, 0.3), None, w=900, style="font-size:30px")
                 + bigline("では、買ったら？", T(33, 0.2), size=64)
                 + '</div>', 0.0)
block("car2", [30, 31, 32, 33], car2)


def soft(T, E):
    return (paper(tag("買っても残る縛り") + '<div class="stack" style="top:96px;gap:24px">'
                  + f'<div class="serif" style="font-size:50px;font-weight:700">{ink("ソフトは、", T(34, 0.2), 0.08)}{land("買っていない", T(34, 0.9), "font-size:90px;font-weight:900")}</div>'
                  + line("使う権利を、借りているだけ", T(34, 2.4), size=30, weight=500, style="color:#5a564f")
                  + card("fact", inkl("中古で売られた車の運転を助ける機能を、／メーカーが遠くから外した（2020年の報道）", T(35, 0.4), 0.04), T(35, 0.15), "車では", w=880)
                  + '<div class="row2" style="gap:24px;align-items:center">'
                  + card("mi", inkl("え、買ったのに？", T(36, 0.2), 0.07), T(36, 0.05), "エイイチ", w=380, anim="wipe")
                  + card("hb", "騒ぎになって、あとで戻された", T(37, 0.3), None, w=480, style="font-size:28px")
                  + '</div>'
                  + f'<div class="serif" style="font-size:52px;font-weight:900">{land("体は自分の物、機能は借り物", T(37, 3.0))}</div>'
                  + '</div>' + src("出どころ：Electrek（2020-02-07）。騒ぎのあと、機能は戻された", T(35, 0.3)), 0.0))
block("soft", [34, 35, 36, 37], soft)


def rule(T, E):
    fly = (T(39, S(39) - 0.3), E(39) + 0.1)
    col = lambda kind, head, sub, t: (f'<div class="card k-{kind}" style="width:430px;opacity:0;animation:pop .45s ease-out {t:.3f}s forwards">'
                                      f'<span class="serif" style="font-size:48px;font-weight:900;line-height:1.3">{head}</span><br><span style="font-size:26px">{sub}</span></div>')
    return (dassen(BG + "ロボ検.jpg", fly[0] + 0.05, fly[1] - 0.05, words="ロボ検、|!開幕。", word_style="top:40px;font-size:130px", src_note=GEM)
            + paper(tag("もう1つの違い") + '<div class="stack" style="top:100px;gap:24px">'
                    + qline("ここまで変えたら、この検査", T(38, 1.6), size=44)
                    + '<div class="row2" style="gap:30px">'
                    + col("fact", "車　ある", "受け皿の制度がある", T(38, 4.0))
                    + col("hb", "屋内のロボット　ない", "決まった手続きがない", T(38, 6.0))
                    + '</div>'
                    + '<div class="row2" style="gap:24px;align-items:center">'
                    + card("hb", "ロボ検を作る", T(39, 0.4), "ハコブくん", w=360, botsu=T(41, 0.4), fade=T(41, 1.0), style="font-size:30px")
                    + card("mi", inkl("誰が検査するの、それ", T(40, 0.2), 0.06), T(40, 0.05), "エイイチ", w=460, anim="wipe")
                    + '</div>'
                    + card("fact", inkl("改造した人が、安全を自分で説明する", T(42, 0.6), 0.05), T(42, 0.3), "手続きがない分", w=760)
                    + '</div>', 0.0, flights=[fly]))
block("rule", [38, 39, 40, 41, 42], rule)


def value(T, E):
    chip = lambda txt, t: (f'<div class="card k-fact" style="width:300px;opacity:0;animation:pop .45s ease-out {t:.3f}s forwards;font-size:32px;padding:20px 10px;white-space:nowrap">{txt}</div>')
    return (paper(tag("いちばん大事な所") + '<div class="center" style="gap:30px">'
                  + f'<div class="serif" style="font-size:44px;font-weight:700">{ink("配膳ロボットの値打ちは、", T(44, 0.2), 0.06)}{land("ソフトの側", T(44, 1.4), "font-size:84px;font-weight:900")}</div>'
                  + f'<div class="row2" style="gap:22px">{chip("地図", T(44, 2.6))}{chip("よけ方", T(44, 3.3))}{chip("遠くからの見守り", T(44, 4.0))}</div>'
                  + card("mi", inkl("アップデートに追いつかない", T(43, 0.3), 0.06), T(43, 0.1), "エイイチの言葉が、本題だった", w=620, anim="wipe")
                  + card("hb", "すれ違い方・迷い方も、ここ", T(45, 0.4), "最初に直したいと言っていた所", w=620, style="font-size:34px;font-weight:700")
                  + '</div>', 0.0))
block("value", [43, 44, 45], value)

# ================= 4 やり取り2：口を開けると、穴も開く（申請書2枚目 →「却下」・脱線③「扉、全開。」） =================
def port(T, E):
    fly = (T(48, S(48) - 0.9), T(49, 0.3))
    return (dassen(BG + "扉全開.jpg", fly[0] + 0.05, fly[1] - 0.05, words="扉、|!全開。", word_style="top:60px;font-size:140px", src_note=GEM)
            + paper(tag("やり取り 2 ／ 外からつなげる口") + '<div class="stack" style="top:84px;gap:22px">'
                    + f'<div style="opacity:1;animation:hide .3s linear {T(47, 0.0):.3f}s forwards;position:absolute;top:30px;left:0;right:0;display:flex;justify-content:center">'
                    + form(2, ["外からつなげる口を、全部開ける", "誰とでも仲良くする"], T(46, 0.2), [T(46, 1.0), T(46, 3.4)]) + '</div>'
                    + f'<div style="opacity:0;animation:show .3s linear {T(47, 0.0):.3f}s forwards;display:flex;flex-direction:column;align-items:center;gap:26px">'
                    + f'<div class="serif" style="font-size:46px;font-weight:700">{ink("口を増やすほど、", T(47, 0.1), 0.07)}{land("穴も増える", T(47, 1.0), "font-size:84px;font-weight:900")}</div>'
                    + card("fact", inkl("2025年、ある配膳ロボットの会社の外部接続は、鍵は求めるのに、／その機体の持ち主かを確かめていなかった", T(48, 0.4), 0.04), T(48, 0.15), "報告されていること", w=900)
                    + '<div class="row2" style="gap:24px;align-items:center">'
                    + card("mi", inkl("よその店のロボットまで、／動かせたってこと？", T(49, 0.4), 0.06), T(49, 0.2), "エイイチ", w=480, anim="wipe")
                    + card("hb", "運ぶ物によっては、笑えない", T(50, 0.4), None, w=430, style="font-size:30px")
                    + '</div>'
                    + card("fact", inkl("開けるのは、メーカーが用意した窓口だけ。／それでも、更新で作り直しになることがある", T(51, 0.4), 0.045), T(51, 0.15), None, w=860)
                    + '</div></div>' + src("出どころ：見つけた人の報告・The Register（2025-08-29）。手口は扱わない", T(48, 0.3)), 0.0, flights=[fly])
            + hanko("却下", E(46) - 0.9, "left:1130px;top:180px", size=80, t_hide=T(47, 0.0))
            + shake_js([(E(46) - 0.55, 0.3)]))
block("port", [46, 47, 48, 49, 50, 51], port)

# ================= 5 山場：満足できない人が増えたら =================
def peak(T, E):
    names = ["有料の性能解放", "非公認の解放業者", "アプリの市場", "公認の調整業者", "修理する権利", "汎用の土台＋自前のソフト", "サポート切れ機の再生", "追加学習"]
    tile = lambda k, txt, t: (f'<div class="card {"k-fact" if k == 7 else "k-hb"}" style="width:220px;height:110px;display:flex;align-items:center;justify-content:center;'
                              f'opacity:0;animation:pop .3s ease-out {t:.3f}s forwards;font-size:24px;padding:8px 10px;font-weight:700">{txt}</div>')
    grid = '<div style="display:grid;grid-template-columns:repeat(4,220px);gap:14px">' + "".join(tile(k, n, T(54, 2.6 + 0.35 * k)) for k, n in enumerate(names)) + '</div>'
    return (paper(tag("満足できない人が増えたら") + '<div class="stack" style="top:84px;gap:22px">'
                  + card("mi", inkl("ロボットが一般的になったら、／標準の性能だけじゃ、満足できない人も出てくる", T(53, 0.4), 0.045), T(53, 0.15), "見立て ・ エイイチ", w=860, anim="wipe")
                  + f'<div class="serif" style="font-size:40px;font-weight:700">{ink("考えられる形は、", T(54, 1.0), 0.06)}{land("8つ", T(54, 2.0), "font-size:72px;font-weight:900")}</div>'
                  + grid
                  + '<div class="row2" style="gap:24px;align-items:center">'
                  + card("mi", inkl("細かいのは、記事に書いといて", T(55, 0.3), 0.06), T(55, 0.1), "エイイチ", w=520, anim="wipe")
                  + card("hb", 'もう書いてあります<div style="font-size:22px;color:#5a564f;margin-top:2px">pixelstomotors.com ／ LOG406</div>', T(56, 0.2), None, w=400, style="font-size:30px")
                  + '</div></div>', 0.0))
block("peak", [52, 53, 54, 55, 56], peak)


def unlock(T, E):
    fly = (T(58, S(58) + 0.05), T(59, 0.3))
    return (dassen(BG + "課金で解放.jpg", fly[0] + 0.05, fly[1] - 0.05, words="課金で|!解放。", word_style="top:60px;font-size:140px", src_note=GEM)
            + paper(tag("車では、もう起きている") + '<div class="stack" style="top:90px;gap:22px">'
                    + line("Model Y（2025年）　部品は替えずに、ソフトの更新だけで速くなる", T(58, 0.6), size=30, weight=700)
                    + f'<div class="num" style="font-size:170px;line-height:1.05"><span style="font-size:80px">約$</span><span id="c2k">0</span></div>'
                    + f'<div class="serif" style="font-size:48px;font-weight:900">{land("同じ体のまま、お金で速くなる", T(59, 0.4))}</div>'
                    + '<div class="row2" style="gap:22px;align-items:center">'
                    + card("mi", inkl("ロボットでもやりそう", T(60, 0.2), 0.06), T(60, 0.05), "エイイチ", w=420, anim="wipe")
                    + card("hb", "払いますか？", T(61, 0.1), "ハコブくん", w=300, style="font-size:32px")
                    + card("mi", f'<span style="font-size:52px;font-weight:900">{ink("払います", T(62, 0.1), 0.06)}</span>', T(62, 0.0), "エイイチ", w=300, anim="wipe")
                    + '</div></div>' + src("出どころ：EVANNEX（2025-06-02）。米国での価格", T(58, 0.6)), 0.0, flights=[fly])
            + stamp("即答", T(63, 0.05), "left:1330px;top:300px;font-size:60px")
            + shake_js([(T(63, 0.4), 0.3)])
            + counter_js("c2k", T(58, 2.4), T(58, 4.2), 2000, dec=0))
block("unlock", [57, 58, 59, 60, 61, 62, 63], unlock)


def learn(T, E):
    return paper(tag("8つのうち、長い目で見た主役") + '<div class="center" style="gap:26px">'
                 + card("hb", inkl("動きを学習で作るロボットが広がれば、／長い目で見た主役は、最後の1つ", T(64, 0.5), 0.045), T(64, 0.2), "Claude の見立て", w=880)
                 + bigline("追加学習", T(65, 1.6), size=140)
                 + line("自分の店のお手本を足して、動きを学ばせ直す", T(65, 0.4), size=32, weight=500)
                 + f'<div class="serif" style="font-size:52px;font-weight:900;margin-top:10px">{ink("改造は、ネジから、", T(66, 0.4), 0.08)}{land("お手本へ。", T(66, 1.8), "font-size:70px")}</div>'
                 + '</div>', 0.0)
block("learn", [64, 65, 66], learn)


def learn_peak(T, E):
    return ('<div style="position:absolute;inset:0;background:#141311"></div>'
            '<div class="paper" style="opacity:.08"></div>'
            '<div class="center" style="left:430px;width:1060px;right:auto;top:64px;height:780px;bottom:auto;gap:40px">'
            '<p class="v" style="color:#e9e5dc;font-size:60px;line-height:1.7;font-weight:500;letter-spacing:.08em;">'
            f'<span data-ink="{T(67, 0.5):.2f},0.1">追加学習の形に、</span><br>'
            f'{land("魅力", T(67, 2.4), "font-weight:900;font-size:96px")}<span data-ink="{T(67, 2.9):.2f},0.1">がある。</span></p>'
            f'<div style="{SANS}font-size:26px;color:#bdb7ab;opacity:0;animation:show .3s linear {T(67, 3.4):.3f}s forwards">エイイチ</div>'
            '</div>'
            + stamp("エイイチの推し", T(67, S(67) + 0.1), "left:1080px;top:600px;font-size:44px")
            + shake_js([(T(67, S(67) + 0.45), 0.3)]))
block("learn_peak", [67, 68], learn_peak)

# ================= 6 やり取り3：半分開いたロボット =================
def open_(T, E):
    box = lambda head, sub, kind, t: (f'<div class="card k-{kind}" style="width:420px;opacity:0;animation:pop .45s ease-out {t:.3f}s forwards">'
                                      f'<span class="serif" style="font-size:44px;font-weight:900;line-height:1.3">{head}</span><br><span style="font-size:26px">{sub}</span></div>')
    return (paper(tag("やり取り 3 ／ 半分開いたロボット") + '<div class="stack" style="top:76px;gap:14px">'
                  + card("mi", inkl("オープン型のロボットも出てきそう。／市場が大きくなったら、差別化で。競争が激しくなるかも", T(69, 0.4), 0.045), T(69, 0.15), "見立て ・ エイイチ", w=880, anim="wipe")
                  + bigline("もう、出ていた", T(70, 0.2), size=56)
                  + line("Unitree（中国のヒューマノイド・四足ロボットのメーカー）", T(71, 0.6), size=26, weight=500, style="color:#5a564f")
                  + line("関節ごとの細かい動きまで、開けている", T(71, 3.0), size=32, weight=700)
                  + line("でも、人型ロボット G1 の、機体の中の計算機は", T(72, 0.6), size=28, weight=500)
                  + '<div class="row2" style="gap:30px">'
                  + box("計算機 1", "メーカーのサービス専用。開発には使えない", "fact", T(72, 3.6))
                  + box("計算機 2", "開発に使える", "hb", T(72, 4.6))
                  + '</div>'
                  + '</div>' + src("出どころ：Unitree G1 開発の手引き", T(71, 0.6)), 0.0))
block("open", [69, 70, 71, 72], open_)


def open2(T, E):
    seg = lambda txt, bg, fg, t: (f'<div class="seg" style="width:440px;padding:60px 10px;font-size:54px;font-family:\'Noto Serif CJK JP\',serif;font-weight:900;'
                                  f'background:{bg};color:{fg};clip-path:inset(0 100% 0 0);animation:wipe .45s ease-out {t:.3f}s forwards">{txt}</div>')
    return paper(tag("Claude の読み") + '<div class="center" style="gap:34px">'
                 + '<div style="display:flex">' + seg("安全は、閉じる", "#22201c", "#f5f3ee", T(73, 1.6)) + seg("動きは、開く", "#ece9e2", "#22201c", T(73, 4.0)) + '</div>'
                 + line("転ばない・止まる、はメーカー。何をどう動かすか、を開ける", T(73, 5.2), size=28, weight=500, style="color:#5a564f")
                 + card("hb", "スマホの iPhone と Android の関係に近い", T(74, 0.4), "Claude の見立て", w=720, style="font-size:32px")
                 + '</div>', 0.0)
block("open2", [73, 74], open2)


def maker(T, E):
    return (paper(tag("もしメーカーなら") + '<div class="center" style="gap:28px">'
                  + card("hb", "エイイチがメーカーなら、どこまで開けますか？", T(75, 0.1), "ハコブくん", w=760, style="font-size:32px")
                  + card("mi", f'<span style="font-size:46px;font-weight:900">{inkl("全部知りたいから、／できる限り分解する", T(76, 0.3), 0.06)}</span>', T(76, 0.1), "エイイチ", w=760, anim="wipe")
                  + f'<div class="serif" style="font-size:44px;font-weight:700">{ink("それは、", T(77, 0.6), 0.08)}{land("開けたい側", T(77, 2.0), "font-size:80px;font-weight:900")}{ink("の答え", T(77, 2.6), 0.08)}</div>'
                  + '</div>', 0.0))
block("maker", [75, 76, 77], maker)


def profit(T, E):
    return (paper(tag("値打ちは、どこへ") + '<div class="center" style="gap:30px">'
                  + card("mi", f'<span style="font-size:44px;font-weight:700">{inkl("利益が残るのは、／作る側より、入れて回す側", T(78, 0.3), 0.06)}</span>', T(78, 0.1), "見立て ・ エイイチ", w=820, anim="wipe")
                  + card("hb", inkl("機体は値下がりし、値打ちは、／学習させた動きと、現場に入れて回す所へ", T(79, 0.6), 0.045), T(79, 0.3), "Claude の見立て（かもしれない）", w=860)
                  + '</div>', 0.0)
            + stamp("この見立ては、エイイチから", T(78, S(78) + 0.1), "left:1010px;top:150px")
            + shake_js([(T(78, S(78) + 0.45), 0.3)]))
block("profit", [78, 79], profit)

# ================= 7 まとめ（散らばった面が3行の表に収まる） =================
SUM_CSS = """@keyframes gather { from { opacity:0; transform: translate(var(--dx),var(--dy)) rotate(var(--r)); } to { opacity:1; transform: none; } }
.trow3 { display:grid; grid-template-columns: 200px 1fr 260px; align-items:center; width:940px; padding:14px 26px; font-family:'Noto Sans CJK JP',sans-serif; font-size:28px; font-weight:500; text-align:left; gap:14px; }
.trow3 b { font-weight:900; font-size:30px; }
.trow3 i { font-style:normal; font-weight:700; }"""


def summ(T, E):
    row = lambda a, b, c, bg, fg, dx, dy, r, t: (f'<div class="trow3" style="background:{bg};color:{fg};--dx:{dx}px;--dy:{dy}px;--r:{r}deg;opacity:0;'
                                                f'animation:gather .9s cubic-bezier(.3,1.2,.4,1) {t:.3f}s forwards"><b>{a}</b><span>{b}</span><i>{c}</i></div>')
    head = (f'<div class="trow3" style="color:#5a564f;font-size:22px;padding:0 26px;opacity:0;animation:show .3s linear {T(81, 0.0):.3f}s forwards">'
            '<b style="font-size:22px">改造の形</b><span>中心</span><i>責任を負うのは</i></div>')
    b3 = lambda txt, t: card("hb", txt, t, None, w=290, botsu=t + 0.25, style="font-size:24px;padding:10px 12px;white-space:nowrap")
    return ('<style>' + SUM_CSS + '</style>'
            + paper(tag("ここまでを、1枚に") + '<div class="stack" style="top:76px;gap:10px">'
                    + head
                    + row("これまで", "機体・ソフトの書き換え", "改造した人", "#22201c", "#f5f3ee", 420, 420, 8, T(81, 0.4))
                    + row("公認の範囲", "設定・外部接続・認められた業者", "メーカーと業者で分ける", "#dcd6c9", "#22201c", 300, -60, -6, T(82, 0.4))
                    + row("これから", "現場のお手本で追加学習", "まだ決まっていない", "#ece9e2", "#22201c", -420, 180, 10, T(83, 0.4))
                    + f'<p class="serif" style="margin-top:14px;text-align:center;font-size:36px;line-height:1.55;font-weight:900;">'
                      f'{ink("改造の行き先は、メーカーが開けた範囲で、", T(84, 2.6), 0.05)}<br>{land("自分の現場に合う動きを、学ばせること。", T(84, 5.0), "font-size:46px")}</p>'
                    + f'<div class="row2" style="gap:20px;margin-top:8px">{b3("5段目まで一気に", T(85, 1.6))}{b3("ロボ検", T(85, 2.0))}{b3("口を全部開ける", T(85, 2.4))}</div>'
                    + '</div>', 0.0))
block("sum", [80, 81, 82, 83, 84, 85], summ)

# ================= 8 残った問い =================
def rest(T, E):
    return paper(tag("残った問い") + '<div class="stack" style="top:70px;gap:14px">'
                 + f'<div class="serif" style="font-size:44px;font-weight:700;line-height:1.5">{ink("あとから学ばせた動きを、", T(87, 1.8), 0.06)}<br>'
                   f'{land("誰が確かめるのか", T(87, 3.4), "font-size:64px;font-weight:900")}</div>'
                 + line("決まりは動き始めているが、ここはまだ、はっきりしない", T(87, 0.6), size=26, weight=500, style="color:#5a564f")
                 + card("mi", inkl("エンジニアと、店の社員やスタッフが、一緒に確かめるのがいちばん。／ただ実際は、コストの面で難しそう", T(89, 0.4), 0.045), T(89, 0.15), "エイイチ", w=900, anim="wipe")
                 + f'<div class="serif" style="font-size:40px;font-weight:700">{ink("良い形と、払える形が、", T(90, 0.2), 0.06)}{land("離れている", T(90, 1.6), "font-size:60px;font-weight:900")}</div>'
                 + card("hb", '考察ノートの全文　<span style="font-size:24px;color:#5a564f">pixelstomotors.com ／ LOG406</span>', T(91, 0.3), "サイトの記事", w=700, style="font-size:28px;padding:10px 30px")
                 + '</div>', 0.0)
block("rest", [86, 87, 88, 89, 90, 91], rest)


def ask(T, E):
    return paper(tag("あなたの店では") + '<div class="center" style="gap:12px">'
                 + qline("あなたの店のロボット、", T(92, 0.2), size=56)
                 + qline("どこまで触っていいか、", T(92, 1.4), size=56)
                 + qline("契約書に書いてありますか。", T(92, 2.8), size=56)
                 + '</div>', 0.0)
block("ask", [92], ask)


def end(T, E):
    return (paper(tag("最後に") + '<div class="center" style="gap:30px">'
                  + form(3, ["病院の廊下で、人とすれ違う動きを、追加学習する"], T(93, 0.6), [T(93, 2.2)])
                  + card("mi", inkl("……それは、ありかもね", T(94, 0.3), 0.07), T(94, 0.1), "エイイチ", w=480, anim="wipe")
                  + '</div>', 0.0)
            + hanko("保留", T(94, S(94) + 0.1), "left:1120px;top:170px", size=80)
            + shake_js([(T(94, S(94) + 0.45), 0.3)]))
block("end", [93, 94, 95], end)

# ================= 9 アウトロ =================
block("outro", [96, 97, 98], None, video=("src/day124_crop.mp4", 18.0, "png/ovl/outro.png"))

assert [i for b in BLOCKS for i in b["sents"]] == list(range(N)), "場面の文の番号が抜けている・重なっている"

# ================= ハコブくん =================
# 表情は narration.py の表情が基本。ここは文の途中で替える所: (文, 文の頭から何秒, 何秒間, 表情)
HK_FACE = [
    (2, 0.0, 2.5, "doubt"),
    (3, 0.0, 1.5, "trouble"),                 # 却下
    (22, S(22), 1.2, "surprise"),             # 一気に → 脱線
    (23, 0.0, 1.0, "surprise"),
    (29, 0.0, 2.0, "surprise"),
    (36, 0.0, 1.5, "surprise"),
    (41, 0.0, 2.2, "dot"),                    # ボツ
    (46, S(46), 1.4, "trouble"),              # 却下（2枚目）
    (55, 0.0, 2.0, "trouble"),
    (62, 0.0, 1.0, "surprise"),
    (67, 0.0, 6.0, "think"),
    (76, 0.0, 3.0, "surprise"),
    (94, S(94), 1.6, "trouble"),              # 保留……
]
HK_FACE = [(A(i, a), A(i, a) + d, f) for i, a, d, f in HK_FACE]

# 体ごとの動き: jump＝驚いて全身で飛び出す / fall＝前のめりにコケる（up で起き上がる） / reflex＝のけぞる
HK_MOVES = [
    dict(kind="reflex", t0=A(3, 0.2), dur=0.9),                          # 却下でのけぞる
    dict(kind="fall", t0=A(41, 0.2), t1=A(42, 0.2), dur=0.5),             # ロボ検ボツでコケる
    dict(kind="jump", t0=A(68, 0.0), dur=1.9),                           # 「……それです」で飛び出す
]
HK_ALPHA = [(A(67, 0.0), tl.end(68) - 2.0, 0.3),                          # 暗い場面では薄く（飛び出す前まで）
            (A(22, S(22) - 0.15), A(23, -0.05), 0.0),                     # 自分で改造の絵にはハコブくんが描いてあるので、重ねない
            (A(39, S(39) - 0.25), A(40, 0.05), 0.0)]                      # ロボ検の絵も同じ

# ================= エイイチ =================
# (文, 文の頭から何秒, 姿) 1腕組み 2うなずく 3指さす 4首をかしげる 5ひらめく 6のけぞって驚く 7半目でツッコむ 8吹き出して笑う
EI_POSE = [
    (0, 0.0, 1), (1, 0.6, 6), (2, 0.0, 7), (3, 0.0, 1), (4, 0.0, 2), (6, 0.0, 4), (7, 0.0, 7), (8, 0.0, 2), (9, 0.0, 1),
    (12, 0.0, 2), (13, 0.0, 1),
    (14, 0.0, 1), (15, 0.0, 3), (16, 0.0, 2), (17, 0.0, 4), (18, 0.0, 3), (19, 0.0, 2),
    (20, 0.0, 1), (22, 0.3, 6), (23, 0.0, 1), (24, 0.0, 2), (25, 0.0, 3), (26, 0.0, 2),
    (27, 0.0, 1), (28, 0.0, 3), (29, 0.0, 8), (30, 0.0, 4), (31, 0.0, 3), (32, 0.0, 2), (33, 0.0, 4),
    (34, 0.0, 1), (35, 0.0, 4), (36, 0.0, 6), (37, 0.0, 1), (37, 3.0, 2),
    (38, 0.0, 1), (39, 0.4, 6), (40, 0.0, 7), (41, 0.3, 8), (42, 0.0, 2),
    (43, 0.0, 5), (44, 0.0, 2), (45, 0.0, 3),
    (46, 0.0, 4), (46, 2.5, 7), (47, 0.0, 1), (48, 0.0, 4), (49, 0.0, 6), (50, 0.0, 1), (51, 0.0, 2),
    (52, 0.0, 1), (53, 0.0, 3), (54, 0.0, 4), (55, 0.0, 7), (56, 0.0, 8),
    (57, 0.0, 1), (58, 0.0, 4), (59, 0.0, 6), (60, 0.0, 3), (61, 0.0, 1), (62, 0.0, 3), (63, 0.0, 8),
    (64, 0.0, 1), (65, 0.0, 2), (66, 0.0, 2), (67, 0.0, 5), (68, 0.0, 3),
    (69, 0.0, 3), (70, 0.0, 6), (71, 0.0, 1), (72, 0.0, 4), (73, 0.0, 2), (74, 0.0, 2),
    (75, 0.0, 4), (76, 0.0, 3), (77, 0.6, 8),
    (78, 0.0, 5), (79, 0.0, 2),
    (80, 0.0, 1), (81, 0.0, 2), (84, 0.0, 2), (85, 0.8, 8),
    (86, 0.0, 1), (87, 0.0, 4), (88, 0.0, 1), (89, 0.0, 3), (90, 0.0, 2), (91, 0.0, 2),
    (92, 0.0, 3), (93, 0.0, 4), (94, 0.0, 2), (95, 0.0, 3),
    (96, 0.0, 2),
]
EI_POSE = sorted((A(i, a), p) for i, a, p in EI_POSE)
# 置き方の替え: (開始, 終了, 置き方)。hide＝出さない / big＝暗い場面で大きく光らせる
EI_PLACE = [
    (A(10), tl.end(11), "hide"),
    (A(67), tl.end(67), "big"),
]

# ================= 効果音 =================
# (文, 文の頭から何秒, 音, ナレーションより何dB)
SE = [
    (0, 0.3, "se_pop", -12), (1, 0.2, "se_pop", -16), (1, 0.6, "se_pop", -16), (1, 1.0, "se_pop", -16),
    (3, 0.25, "se_pop", -6),                                                     # 却下
    (9, 1.8, "se_pop", -12),
    (22, S(22) - 0.2, "se_whoosh", -10), (23, -0.05, "se_whoosh", -14), (22, 99, "se_pop", -12),
    (23, 0.3, "se_pop", -10),                                                    # 切れ目
    (33, 0.2, "se_pop", -12),
    (39, S(39) - 0.3, "se_whoosh", -10), (40, 0.0, "se_whoosh", -14), (41, 0.2, "se_whoosh", -12), (41, 0.45, "se_pop", -6), (41, 0.4, "se_pop", -12),
    (46, 0.2, "se_pop", -12), (46, S(46) - 0.0, "se_pop", -6),                   # 申請書2枚目・却下
    (48, S(48) - 0.9, "se_whoosh", -10), (49, 0.3, "se_whoosh", -14),
    (54, 2.0, "se_pop", -12),
    (58, S(58) + 0.05, "se_whoosh", -10), (59, 0.3, "se_whoosh", -14), (63, 0.05, "se_pop", -8),
    (65, 1.6, "se_pop", -10),
    (67, S(67) + 0.1, "se_pop", -8), (68, 0.0, "se_rise", -10),
    (70, 0.2, "se_pop", -12),
    (78, S(78) + 0.1, "se_pop", -8),
    (85, 1.85, "se_pop", -14), (85, 2.25, "se_pop", -14), (85, 2.65, "se_pop", -14),
    (93, 0.6, "se_pop", -12), (94, S(94) + 0.1, "se_pop", -6),                   # 保留
]
SE = [s for s in SE if s[1] != 99]

# 一覧の画像に撮る時刻（場面の頭からの秒）。書かない場面は最後の0.4秒前
def _rel(b, i, off=0.0):
    return tl.start(i) - _tl.start(b["sents"][0]) + off
SHEET = {}
for _b in BLOCKS:
    _end = _tl.end(_b["sents"][-1]) - _tl.start(_b["sents"][0]) - 0.4
    SHEET[_b["name"]] = [_end]
_B = {b["name"]: b for b in BLOCKS}
SHEET["hook"] = [_rel(_B["hook"], 2, 1.5), SHEET["hook"][0]]
SHEET["steps"] = [_rel(_B["steps"], 21, 7.5), _rel(_B["steps"], 22, S(22) + 0.6), SHEET["steps"][0]]
SHEET["rule"] = [_rel(_B["rule"], 39, S(39) + 0.4), _rel(_B["rule"], 41, 0.6), SHEET["rule"][0]]
SHEET["port"] = [_rel(_B["port"], 46, S(46) + 0.2), _rel(_B["port"], 48, S(48) - 0.2), SHEET["port"][0]]
SHEET["unlock"] = [_rel(_B["unlock"], 58, S(58) + 0.6), SHEET["unlock"][0]]
SHEET["learn_peak"] = [_rel(_B["learn_peak"], 67, S(67) + 0.5), _rel(_B["learn_peak"], 68, 0.8)]
SHEET["end"] = [SHEET["end"][0]]
SHEET["title"] = [2.0]
```

### sound.py（BGM と章の効果音。効果音の時刻は scenes_kabe.py の SE）

```python
# 音の置き方（audio_mix.py が読む）。効果音の時刻は scenes_kabe.py の SE に書く（場面の動きと同じ所で決めるため）
# 書き写した元: 頭脳は脳のように分かれる/sound.py
from scenes_kabe import SE as _SE

BGM = [
    dict(file='sound/bgm_明るいB_2.mp3', start=0, end=None, level=-26, gap=-19, fade_in=0.6, fade_out=3.0),   # 考察回なのでいつもより3dB小さく
]

SE = list(_SE)

CHAPTER_SE = dict(file='se_chapter_marimba', files={'title': 'se_chapter_glock'}, level=-10, offset=-0.3,
                  scenes=['title', 'bring', 'why', 'easy', 'who', 'iris_ei', 'sum', 'outro'])
```

### gen_bg.py（脱線の絵とサムネイルの背景を Gemini の画像の API で作る）

```python
#!/usr/bin/env python3
"""脱線の絵を Gemini の画像の API で作る（16:9）。2026-11-02「どこまでいじっていい」
使い方: python3 gen_bg.py <名前>   （<名前>_プロンプト.txt を読み、<名前>_refs.txt に参照画像のパスを1行ずつ）
鍵は KIOXIA/gemini_api_key.txt から読むだけ（表示しない）"""
import sys, json, base64, time, urllib.request, mimetypes, os, datetime
MODEL = "gemini-3.1-flash-image"
key = next(open(p).read().strip() for p in [os.path.expanduser("~/mnt/KIOXIA/gemini_api_key.txt"), "/Volumes/KIOXIA/gemini_api_key.txt"] if os.path.exists(p))
name = sys.argv[1]
parts = []
rf = f"{name}_refs.txt"
if os.path.exists(rf):
    for line in open(rf, encoding="utf-8"):
        line = line.strip()
        if not line: continue
        label, path = line.split("=", 1)
        parts.append({"text": f"Reference image — {label}:"})
        parts.append({"inlineData": {"mimeType": mimetypes.guess_type(path)[0] or "image/png", "data": base64.b64encode(open(path, "rb").read()).decode()}})
parts.append({"text": open(f"{name}_プロンプト.txt", encoding="utf-8").read()})
body = {"contents": [{"role": "user", "parts": parts}], "generationConfig": {"responseModalities": ["IMAGE"], "imageConfig": {"aspectRatio": "16:9", "imageSize": "2K"}}}
req = urllib.request.Request(f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL}:generateContent", data=json.dumps(body).encode(),
                             headers={"Content-Type": "application/json", "x-goog-api-key": key})
t = time.time()
try:
    res = json.load(urllib.request.urlopen(req, timeout=170))
except urllib.error.HTTPError as e:
    print("失敗", e.code, e.read().decode()[:600]); sys.exit(1)
stamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
img = next((p["inlineData"] for c in res.get("candidates", []) for p in c.get("content", {}).get("parts", []) if "inlineData" in p), None)
out = f"{name}_{stamp}." + ("jpg" if img and "jpeg" in img["mimeType"] else "png")
if img: open(out, "wb").write(base64.b64decode(img["data"]))
fin = [c.get("finishReason") for c in res.get("candidates", [])]
open(f"log_{name}.txt", "a").write(f"{out if img else 'なし'} {time.time()-t:.1f}秒 {fin}\n")
print(out if img else "画像なし", fin)
```

### 脱線の絵のプロンプト：自分で改造（自分で改造_プロンプト.txt）

```text
Black and white Japanese manga illustration.
ART STYLE: match the attached reference manga page from the series "Hakobu" EXACTLY — the same clean, slightly realistic Japanese manga line art, the same line weight, the same halftone-dot screentone and flat gray tones, the same realistic proportions and faces for people, the same crisp white backgrounds. Do NOT copy any text or speech bubbles from the reference page.
USE THE ATTACHED FRONT AND SIDE VIEWS for the robot "Hakobu" — match its body shape, screen face (simple black screen with white oval eyes and a small smile line), round side window, wheels, line weight and screentone exactly. Hakobu is a delivery robot: a rounded box body with a screen for a face. Hakobu has NO arms and NO hands.
Scene: Hakobu is trying to remodel itself, alone, in a small workshop corner. Because Hakobu has no arms, a wrench and a screwdriver are strapped to the top of its body with tape like a silly hat. Its rear access panel has popped wide open, and loose screws, springs, a coiled cable and a small circuit board are scattered on the floor around its wheels. A tiny puff of smoke rises from the open panel. Hakobu's screen shows proud, happy closed-eye arcs (very pleased with itself), completely unaware of the mess.
Comedic, a little reckless.
Keep the upper center area relatively plain so text can be placed over it. No text, no letters, no numbers, no symbols, no question marks, no sound-effect lettering, no speech bubbles anywhere in the image. All signs and posters are blank. One wide 16:9 panel, the artwork fills the whole image with no panel border and no frame line.
```

### 脱線の絵の参照画像：自分で改造（自分で改造_refs.txt）

```text
manga page from the series Hakobu (style reference)=/sessions/rcw-01levexwv8zdupftc6odusjp/mnt/PixelstoMotors/ハコブくん/日本版SNS投稿/第105話_本編.png
Hakobu front view=/sessions/rcw-01levexwv8zdupftc6odusjp/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/キャラ/hakobu_front.png
Hakobu side view=/sessions/rcw-01levexwv8zdupftc6odusjp/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/キャラ/hakobu_side.png
```

### 脱線の絵のプロンプト：ロボ検（ロボ検_プロンプト.txt）

```text
Black and white Japanese manga illustration.
ART STYLE: match the attached reference manga page from the series "Hakobu" EXACTLY — the same clean, slightly realistic Japanese manga line art, the same line weight, the same halftone-dot screentone and flat gray tones, the same realistic proportions and faces for people, the same crisp white backgrounds. Do NOT copy any text or speech bubbles from the reference page.
USE THE ATTACHED FRONT AND SIDE VIEWS for the robot "Hakobu" — match its body shape, screen face (simple black screen with white oval eyes and a small smile line), round side window, wheels, line weight and screentone exactly. Hakobu is a delivery robot: a rounded box body with a screen for a face. Hakobu has NO arms and NO hands.
Scene: an inspection line that looks exactly like a Japanese car inspection station (a garage lane with a painted floor line, a roller test bed and overhead lights), but the vehicles waiting in line are robots: first a slim restaurant serving robot with tray shelves, then a round floor-cleaning robot, and last Hakobu. A serious middle-aged human inspector in work overalls stands beside the line holding a clipboard and a pen, looking at the robots with a puzzled face. Hakobu's screen shows nervous eyes and a small sweat drop.
Comedic, slightly absurd.
Keep the upper center area relatively plain so text can be placed over it. No text, no letters, no numbers, no symbols, no question marks, no sound-effect lettering, no speech bubbles anywhere in the image. All signs and posters are blank. One wide 16:9 panel, the artwork fills the whole image with no panel border and no frame line.
```

### 脱線の絵の参照画像：ロボ検（ロボ検_refs.txt）

```text
manga page from the series Hakobu (style reference)=/sessions/rcw-01levexwv8zdupftc6odusjp/mnt/PixelstoMotors/ハコブくん/日本版SNS投稿/第105話_本編.png
Hakobu front view=/sessions/rcw-01levexwv8zdupftc6odusjp/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/キャラ/hakobu_front.png
Hakobu side view=/sessions/rcw-01levexwv8zdupftc6odusjp/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/キャラ/hakobu_side.png
```

### 脱線の絵のプロンプト：扉全開（扉全開_プロンプト.txt）

```text
Photorealistic wide cinematic photograph, 16:9 landscape.
An old traditional Japanese wooden mansion at dusk, seen from the garden. Every single door, sliding door, window, shutter and the front gate is thrown wide open at the same time, so you can see straight through the empty rooms to the other side. Warm orange sunset light, long shadows, a few leaves blowing in. Nobody is there. The mood is quietly uneasy and a little funny: "everything is wide open".
Keep the upper center area calm (sky and roof) so large text can be placed over it. One continuous seamless photograph.
No people. No text, no letters, no signs, no logos. ABSOLUTELY NO TEXT OR WRITING OF ANY KIND ANYWHERE.
```

### 脱線の絵のプロンプト：課金で解放（課金で解放_プロンプト.txt）

```text
Photorealistic wide cinematic photograph, 16:9 landscape, dramatic studio lighting on a dark background.
A heavy antique wooden treasure chest with iron bands, firmly locked with a big padlock. Right next to the keyhole, a modern coin slot has been bolted onto the chest, like a vending machine. A human hand enters from the right side of the frame and is just about to drop a shiny gold coin into the slot. A faint golden glow leaks out from the gap under the lid.
Slightly absurd and funny. The chest sits in the lower half of the frame; keep the upper center area dark and plain so large text can be placed over it.
No text, no letters, no numbers, no logos, no currency symbols. ABSOLUTELY NO TEXT OR WRITING OF ANY KIND ANYWHERE.
```

### サムネイルの背景のプロンプト（改造ロボ_プロンプト.txt）

```text
A photorealistic, cinematic photograph for a YouTube thumbnail background. Inside a modern Japanese family restaurant after closing time, dim warm light. In the center-right of the frame: a white boxy restaurant serving robot (generic design, no brand) with its rear access panel swung wide open, revealing glowing circuit boards and neatly bundled cables inside. A screwdriver and a small wrench lie on the floor next to its wheels. A heavy red padlock hangs from the latch of the opened panel, as if someone opened it anyway. The robot's face screen glows with simple surprised eyes.
The left 45 percent of the image must be calm, darker, softly blurred restaurant background (for large text added later). The bottom-right corner should also be relatively plain.
Seamless single photograph. No text, no letters, no numbers, no logos, no readable signs, no people, no watermark. 16:9.
```

### thumb_A.py（サムネイル A 案・決めた案。真ん中の文字を大きくした版）

```python
# サムネイル A 案（2026-11-02）: つかみ。改造すると責任は改造した人へ
PHOTO = 'サムネ素材/改造ロボ.jpg'
PHOTO_POS_FULL = '50% 50%'
HAKOBU = True
KICKER = '配膳ロボットを改造したら'
LINES = ['責任は', '<span style="color:#F5A04A">改造した人</span>', '']
LABEL = '却下です……'
VARIANTS = ['photo4']
SIZE_L1, SIZE_L2, SIZE_L3 = 200, 210, 10
TXT_TOP = 60
HK_H = 560
LABEL_BOTTOM = 250
```

### thumb_B.py（サムネイル B 案）

```python
# サムネイル B 案（2026-11-02）: 車との違い。買っても、ソフトは借り物
PHOTO = 'サムネ素材/改造ロボ.jpg'
PHOTO_POS_FULL = '50% 50%'
HAKOBU = True
KICKER = 'ロボット、買っても'
LINES = ['ソフトは', '<span style="color:#F5A04A">借り物</span>', 'だった']
LABEL = 'どこまで触れる？'
VARIANTS = ['photo4']
SIZE_L1, SIZE_L2, SIZE_L3 = 130, 200, 120
TXT_TOP = 60
HK_H = 560
LABEL_BOTTOM = 200
```

### 声の設定：エイイチ（voice_eleven/エイイチ/設定.json）

```json
{
 "voice": "Sryf5xKmMsQChU3HsLhZ",
 "name": "エイイチ",
 "lines": "2,7,15,18,25,28,30,31,36,40,49,53,55,60,62,67,69,76,78,89,94",
 "model": "eleven_v4",
 "seed": 7,
 "stability": 0.55,
 "similarity": 0.75,
 "trial": "",
 "time": "2026-11-02 08:40"
}
```

### 声の設定：ハコブ（voice_eleven/ハコブ/設定.json）

```json
{
 "voice": "gWJpc8whMBqnF7Y0kc9f",
 "name": "ハコブ",
 "lines": "0,1,3,4,5,6,8,9,10,11,12,13,14,16,17,19,20,21,22,23,24,26,27,29,32,33,34,35,37,38",
 "model": "eleven_v4",
 "seed": 7,
 "stability": 0.55,
 "similarity": 0.75,
 "trial": "",
 "time": "2026-11-02 08:41"
}
```

### 声の設定：ハコブ2（voice_eleven/ハコブ2/設定.json）

```json
{
 "voice": "gWJpc8whMBqnF7Y0kc9f",
 "name": "ハコブ2",
 "lines": "39,41,42,43,44,45,46,47,48,50,51,52,54,56,57,58,59,61,63,64,65,66,68,70,71,72,73,74,75,77",
 "model": "eleven_v4",
 "seed": 7,
 "stability": 0.55,
 "similarity": 0.75,
 "trial": "",
 "time": "2026-11-02 08:42"
}
```

### 声の設定：ハコブ3（voice_eleven/ハコブ3/設定.json）

```json
{
 "voice": "gWJpc8whMBqnF7Y0kc9f",
 "name": "ハコブ3",
 "lines": "79,80,81,82,83,84,85,86,87,88,90,91,92,93,95,96,97,98",
 "model": "eleven_v4",
 "seed": 7,
 "stability": 0.55,
 "similarity": 0.75,
 "trial": "",
 "time": "2026-11-02 08:43"
}
```

### 声の設定：直し1（冒頭の法律の1文・voice_eleven/直し1/設定.json）

```json
{
 "voice": "gWJpc8whMBqnF7Y0kc9f",
 "name": "直し1",
 "lines": "4",
 "model": "eleven_v4",
 "seed": 7,
 "stability": 0.55,
 "similarity": 0.75,
 "trial": "",
 "time": "2026-11-02 09:24"
}
```

### 声の設定：直し2（ロボ検の文・voice_eleven/直し2/設定.json）

```json
{
 "voice": "gWJpc8whMBqnF7Y0kc9f",
 "name": "直し2",
 "lines": "39",
 "model": "eleven_v4",
 "seed": 7,
 "stability": 0.55,
 "similarity": 0.75,
 "trial": "",
 "time": "2026-11-02 09:27"
}
```

### ロボ検の読みの試し（voice_eleven/試し_ロボ検/試し.txt）

```text
試し_01: では、ロボットにも車検を作りましょう。名前は、ロボケンです。
試し_02: では、ロボットにも車検を作りましょう。名前は、ろぼけんです。
試し_03: では、ロボットにも車検を作りましょう。名前は、ロボ・ケンです。
試し_04: では、ロボットにも車検を作りましょう。名前は、ロボット車検、略して、ロボケンです。
```

### 書き出しのログ：v2（python3 kabe_kit.py build の出力）

```text
  rule: 874コマ（撮影 251）
BGM 1本・効果音 38個 → mix.wav
    0.30秒  文000  se_pop  -12dB  「エイイチ、お願いがあります。」
    2.57秒  文001  se_pop  -16dB  「僕を、改造してください。」
    2.97秒  文001  se_pop  -16dB  「僕を、改造してください。」
    3.37秒  文001  se_pop  -16dB  「僕を、改造してください。」
    8.75秒  文003  se_pop  -6dB  「……場合によっては、改造した人だ」
   36.83秒  文009  se_pop  -12dB  「今日は、答えを伝える回ではありま」
  131.37秒  文022  se_whoosh  -10dB  「では僕は、5段目まで、一気に。」
  133.02秒  文023  se_whoosh  -14dB  「……3段目と4段目の間に、切れ目」
  133.37秒  文023  se_pop  -10dB  「……3段目と4段目の間に、切れ目」
  200.30秒  文033  se_pop  -12dB  「問題は、買った場合です。」
  240.70秒  文039  se_whoosh  -10dB  「では、ロボットにも車検を作りまし」
  242.50秒  文040  se_whoosh  -14dB  「誰が検査するの、それ。」
  245.13秒  文041  se_whoosh  -12dB  「……そこが、まだ誰もいません。」
  245.38秒  文041  se_pop  -6dB  「……そこが、まだ誰もいません。」
  245.33秒  文041  se_pop  -12dB  「……そこが、まだ誰もいません。」
  274.27秒  文046  se_pop  -12dB  「では僕は、外からつなげる口を、全」
  280.03秒  文046  se_pop  -6dB  「では僕は、外からつなげる口を、全」
  295.17秒  文048  se_whoosh  -10dB  「2025年、ある配膳ロボットの会」
  297.57秒  文049  se_whoosh  -14dB  「よその店のロボットまで、動かせた」
  326.70秒  文054  se_pop  -12dB  「満足できない理由で、行き先が分か」
  353.58秒  文058  se_whoosh  -10dB  「車のModel Yでは、2025」
  355.03秒  文059  se_whoosh  -14dB  「……同じ体のまま、お金で速くなる」
  363.62秒  文063  se_pop  -8dB  「即答でした。」
  375.73秒  文065  se_pop  -10dB  「自分の店のお手本を足して、動きを」
  395.07秒  文067  se_pop  -8dB  「追加学習で、自分の現場専用の動き」
  395.97秒  文068  se_rise  -10dB  「……それです。」
  409.73秒  文070  se_pop  -12dB  「調べたら、もう出ていました。」
  461.00秒  文078  se_pop  -8dB  「ロボットで利益が残るのは、作る側」
  506.02秒  文085  se_pop  -14dB  「ちなみに、ボツになった3つは、全」
  506.42秒  文085  se_pop  -14dB  「ちなみに、ボツになった3つは、全」
  506.82秒  文085  se_pop  -14dB  「ちなみに、ボツになった3つは、全」
  551.60秒  文093  se_pop  -12dB  「……では最後に。病院の廊下で、人」
  559.63秒  文094  se_pop  -6dB  「……それは、ありかもね。」
   42.80秒  文010  se_chapter_glock  -10dB  「Pixels to Motors」
   71.73秒  文014  se_chapter_marimba  -10dB  「きっかけは、こんな疑問でした。」
   85.37秒  文016  se_chapter_marimba  -10dB  「これは、実物を改造した話ではあり」
  471.53秒  文080  se_chapter_marimba  -10dB  「ここまでを、1枚にまとめます。」
  563.33秒  文096  se_chapter_marimba  -10dB  「この動画が役に立ったら、高評価と」
  900/17392
  1800/17392
  2700/17392
  3600/17392
  4500/17392
  5400/17392
  6300/17392
  7200/17392
  8100/17392
  9000/17392
  9900/17392
  10800/17392
  11700/17392
  12600/17392
  13500/17392
  14400/17392
  15300/17392
  16200/17392
  17100/17392
frames 17392 = 579.7s  確認用 24.0MB (crf 29)
```

### 概要欄_下書き.md（目次の時間は {t:文番号} で書き、description.py が置きかえる）

```markdown
<!--
YouTube の概要欄の下書き（2026-11-02「どこまでいじっていい」＝考察回「壁打ちの型」の3本目・もとは LOG406）。{t:文番号} は、その文が始まる時間に置きかわる（文番号は 0 から数える）。
python3 <_制作ベース>/description.py で 概要欄.txt を作り、それを貼る。
-->
配膳ロボットや掃除ロボットの動きを、自分の店に合わせて変えたい。では、メーカーのロボットは、どこまで触っていいのか。エイイチの疑問に、案内役のハコブくんが付き合う「考察回」です。答えを伝える回ではなく、考えている途中をそのまま見せます。触り方の5段階と切れ目、車との違い（ソフトは買っていない）、外からつなげる口と穴、満足できない人が増えたときの8つの形、半分開いたロボットまでを考えました。

詳しい調べと考えた道すじは、サイトの記事にまとめています。
記事：配膳ロボットの次の改造は、追加学習になる ― メーカー製品を触る損得 https://pixelstomotors.com/record/service-robot-modding

▼目次
0:00 改造申請書、却下
{t:10} この番組について
{t:14} 持ち込み：触り方の5段階
{t:27} やり取り1：車とサブスク
{t:46} やり取り2：外からつなげる口
{t:52} 満足できない人が増えたら
{t:69} やり取り3：半分開いたロボット
{t:80} まとめ
{t:86} 残った問い
{t:96} エンディング

▼出どころ
・EU で改変した人が製造者とみなされる条件：EU 機械規則（Regulation (EU) 2023/1230）第18条。全面適用は2027年1月20日 https://eur-lex.europa.eu/eli/reg/2023/1230/oj
・日本で責任を負う側（業として製造・加工・輸入した者）：製造物責任法 第2条・第3条
・保安基準に合わない車の改造の禁止：道路運送車両法 第99条の2
・中古で売られた車の運転支援機能を、メーカーが遠くから外した例（2020年。騒ぎのあと戻された）：Electrek（2020年2月7日） https://electrek.co/2020/02/07/tesla-takes-away-autopilot-used-car-sold-dealer/
・配膳ロボットの外部接続で、持ち主の確認がなかった件（2025年）：The Register（2025年8月29日） https://www.theregister.com/2025/08/29/pudu_robots_hackable/ 。動画では会社名と手口は扱いません
・Model Y の約2,000ドルの加速の更新（2025年・米国の価格）：EVANNEX（2025年6月2日） https://evannex.com/blogs/news/tesla-offers-acceleration-boost-upgrade-for-the-new-model-y
・Unitree G1 の計算機が2つに分かれている話：Unitree G1 開発の手引き https://support.unitree.com/home/en/G1_developer/quick_development

▼この動画について
・実物のロボットを改造した記録ではありません。Claude と対話しながら、決まり・契約・メーカーの開け方の実例を調べて考えた記録です
・法律の話は、調べた範囲の理解です。専門家の判断ではありません。実際に手を入れるときは、メーカーとの契約と、専門家に確かめてください
・エイイチのセリフは、記事を書く前のやり取りで本人が話した言葉と、動画のために本人に聞いた答えを、話し言葉に整えたものです。短い相づちやツッコミの一部は、本人が了承した案です
・触り方の5段階、8つの形、「安全は閉じ、動きは開く」、追加学習が長い目で見た主役になるという見方は、ハコブくん（Claude）の整理と見立てです

▼この番組について
「フィジカルAI解説｜Pixels to Motors」は、フィジカルAIのニュースや実機の記録から、現場で使える形を考える番組です。
エイイチは、本業のデザイナーをしながら、ロボティクスの研究と開発をしています。その中で分かったことと、そこから考えた見立てを、案内役のハコブくんがお伝えします。

・ナレーションは AI 音声（ElevenLabs）です。話しているのは Pixels to Motors のキャラクター、ハコブくんです
・エイイチの声も AI 音声で、本人の声ではありません
・自分で改造するハコブくん・ロボ検のマンガの絵、扉が全開の屋敷・課金の宝箱の絵、サムネイルの背景は、生成AI（Gemini）で作成しています
・実機の映像はエイイチが撮ったものです

▼ロボットの開発・導入のご相談
メール：pixelstomotors@adaptdesign.jp

▼リンク
Webサイト：https://pixelstomotors.com
4コママンガ『ハコブくん』：https://pixelstomotors.com/manga-archive
GitHub：https://github.com/PixelstoMotors
X：https://x.com/adaptdesignjp
LinkedIn：https://www.linkedin.com/in/eiichi-shimazu

チャンネル登録・高評価もよろしくお願いします。コメントは、エイイチが全部読んでいます。

#フィジカルAI #配膳ロボット #ロボット
```

### kabe_short.py（縦長ショート3本。spin・dash・frenzy の入りの動きを足した版2）

```python
# -*- coding: utf-8 -*-
"""考察回「壁打ちの型」の縦長ショート（1080×1920）・2版。
2026-11-02 エイイチの指摘で作り直した:
  「おれが上半身で、ハコブくんが全身は変」→ エイイチも全身のドット絵（src/ei/エイイチ_全身_*_170px_v1.png・Gemini で写真の服装から作った）
  「この始まりだとすぐ離脱しそう」「宇宙・紙の山・引退式の背景を使って、ハコブくんが大きく動く所から始める」
    → 最初の数秒は、画面いっぱいの絵の前で、大きなハコブくん（高さ900）が動く。本題に入ったら、絵を暗くして紙（考察ノート）を出し、
      ハコブくんは左下の全身（高さ400）に戻る
画面の割り振りは video_rules.md 9-2: 0〜150 何も置かない／170〜440 見出し／480〜1130 紙／左下 ハコブくん・右下 エイイチ（足もと y=1640）／字幕は2人の間の上
使い方: python3 kabe_short.py [番号 ...]
"""
import os, sys, json, wave, subprocess, html as _html
import numpy as np
from PIL import Image
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import kabe_kit as K
from kabe_kit import ink, land, card, counter_js, shake_js, ease, blend, rotate_about, hk, FPS, SR
sys.path.insert(1, K.BASE)
import short_kit as SK
from scene_kit import LOGO

W, H = 1080, 1920
SANS = "font-family:'Noto Sans CJK JP',sans-serif;"
FOOT = 1880                     # 2人の足もと（下の方まで使ってよい・2026-11-02 エイイチ）
SUB_MID = 1150                  # 字幕の真ん中
SUB_CX, SUB_MAXC = 540, 11       # 字幕は画面の真ん中（2026-11-02 エイイチの指摘で 470 から直した）・1行11字
EI_SCALE, EI_X = 4.5, None         # エイイチの全身（170px の4.5倍＝高さ765・右端に寄せる）。身長はエイイチ 1700mm・ハコブくん 1150mm（2026-11-02 エイイチ）
HK_SMALL_H, HK_X = 515, 10      # ふだんのハコブくん（全身）。765 × 1150/1700
HK_BIG_H = 900                  # 最初の数秒の大きなハコブくん
EI_POSES = {1: "立つ", 2: "腕組み", 3: "驚く", 4: "笑う"}

VCSS = """
html, body { width:1080px; height:1920px; }
.f { width:1080px; height:1920px; }
.vhead { position:absolute; left:60px; right:60px; top:170px; height:270px; display:flex; flex-direction:column; justify-content:center; align-items:center; text-align:center; z-index:5; }
.vhead img { height:54px; align-self:flex-start; margin-bottom:18px; filter:invert(1) drop-shadow(0 0 6px rgba(0,0,0,.8)); opacity:.9; }
.vhead .q { font-family:'Noto Sans CJK JP',sans-serif; font-size:84px; font-weight:900; line-height:1.2; color:#f5f3ee; text-shadow:0 0 18px rgba(0,0,0,.9), 0 4px 0 #141311; }
.vpaper { position:absolute; left:100px; top:480px; width:880px; height:590px; background:#f5f3ee; box-shadow:0 30px 60px rgba(0,0,0,.45); opacity:0; }
.vpaper .card { font-size:40px; }
.vpaper .card small { font-size:24px; }
.vstack { position:absolute; left:0; right:0; top:40px; bottom:40px; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:22px; }
.bgimg { position:absolute; inset:0; background-repeat:no-repeat; background-color:#fff; }
@keyframes veil { to { opacity:.74; } }
@keyframes pIn { from { opacity:0; transform:translateY(60px) rotate(-1.5deg); } to { opacity:1; transform:none; } }
.vbig { position:absolute; left:0; right:0; text-align:center; font-family:'Noto Sans CJK JP',sans-serif; font-weight:900; color:#fff; line-height:1.15;
        -webkit-text-stroke:5px #141311; paint-order:stroke fill; text-shadow:0 10px 0 #c4472a; z-index:6; }
.vbig span { display:inline-block; opacity:0; }
"""


def vpage(head, body):
    return K.page(body + f'<div class="vhead"><img src="{LOGO}"><div class="q">{head}</div></div>', VCSS)


def bg(img, size_h, pos_x, pos_y, t_veil):
    p = "file://" + os.path.abspath(img)
    return (f'<div class="bgimg" style="background-image:url(\'{p}\');background-size:auto {size_h}px;background-position:{pos_x}px {pos_y}px"></div>'
            f'<div style="position:absolute;inset:0;background:#141311;opacity:0;animation:veil .5s ease {t_veil:.3f}s forwards"></div>'
            '<div style="position:absolute;right:100px;top:446px;font-family:\'Noto Sans CJK JP\';font-size:22px;color:#fff;opacity:.85;text-shadow:0 0 6px #000;z-index:7">絵は生成AI（Gemini）で作成</div>')


def paper(inner, t_in):
    return f'<div class="vpaper" style="animation:pIn .45s cubic-bezier(.2,1.2,.4,1) {t_in:.3f}s forwards">{inner}</div>'


def grp(inner, t_in=0.0, t_out=None):
    an = f"show .01s linear {t_in:.3f}s forwards" + (f", hide .2s linear {t_out:.3f}s forwards" if t_out is not None else "")
    return f'<div class="vstack" style="opacity:0;animation:{an}">{inner}</div>'


def line(text, t, size=40, weight=700, step=0.05, style=""):
    return f'<div style="{SANS}font-size:{size}px;font-weight:{weight};text-align:center;{style}">{ink(text, t, step)}</div>'


def words(rows, t0, t_out, top, size=130, gap=0.32):
    """大きな一言を1行ずつ叩きつける。rows は [(文字, 色)]"""
    out = []
    for k, (txt, col) in enumerate(rows):
        out.append(f'<span style="color:{col};animation:boom .32s cubic-bezier(.2,1.8,.4,1) {t0 + gap * k:.3f}s forwards, hide .2s linear {t_out:.3f}s forwards">{txt}</span>')
    return f'<div class="vbig" style="top:{top}px;font-size:{size}px;transform:rotate(-4deg)">{"<br>".join(out)}</div>'


def bigword(text, t, t_out, top, size=300, color="#c4472a", rot=-8):
    return (f'<div class="big" style="top:{top}px;font-size:{size}px;color:{color};-webkit-text-stroke:10px #fff;paint-order:stroke fill;'
            f'text-shadow:0 14px 0 #141311;transform:rotate({rot}deg);z-index:6"><span style="animation:boom .3s cubic-bezier(.2,1.8,.4,1) {t:.3f}s forwards, '
            f'hide .2s linear {t_out:.3f}s forwards">{text}</span></div>')


WHITE, GOLD = "#ffffff", "#ffd84a"


# ====================== 3本の中身（T(k, 秒) は k 番目の文の頭から。k=-1 は動画の頭） ======================
# 2026-11-02「どこまでいじっていい」用に書き替えた（書き写した元: 頭脳は脳のように分かれる/kabe_short.py）
FORM_CSS = """<style>
.vform { width:700px; background:#fffdf8; border:3px solid #22201c; padding:18px 30px 22px; font-family:'Noto Sans CJK JP',sans-serif; color:#22201c; text-align:left; }
.vform h3 { font-family:'Noto Serif CJK JP',serif; font-size:52px; font-weight:900; text-align:center; letter-spacing:.2em; }
.vform .it { font-size:36px; font-weight:700; padding:6px 0; border-top:2px solid #e2ddd2; }
</style>"""


def short_kaizo(T, S):
    tp = T(1, 0.0)   # エイイチの「誰の責任？」で、絵を暗くして紙を出す
    return vpage("改造したら、<br>責任は誰？", (
        bg("src/bg/自分で改造.jpg", 1920, -2333, 0, tp)
        + words([("僕を、", WHITE), ("改造して", GOLD)], T(0, 0.2), tp, 560, size=140, gap=0.5)
        + paper(FORM_CSS + grp(f'<div class="vform"><h3>改造申請書</h3><div class="it">☑ 速さの上限を外す</div><div class="it">☑ 夜も走る</div></div>'
                    + card("mi", ink("それ、事故が起きたら、誰の責任？", T(1, 0.3), 0.06), T(1, 0.1), "エイイチ", w=760, anim="wipe"),
                    tp + 0.1, T(3, 0.0))
                + grp(card("fact", ink("EU：想定外の改変で危険を増やした人は、メーカーと同じ義務", T(3, 0.4), 0.04), T(3, 0.2), None, w=780)
                      + card("fact", ink("日本：仕事として加工して渡した業者も、責任を負う側", T(3, 3.0), 0.04), T(3, 2.8), None, w=780)
                      + line("法律の話は、調べた範囲の理解です", T(3, 0.4), size=26, weight=500, style="color:#5a564f"), T(3, 0.05)), tp)
        + bigword("却下", T(2, 0.2), T(3, 0.0), 640, size=300)
        + shake_js([(T(2, 0.5), 0.3)])))


def short_robokan(T, S):
    tp = T(1, 0.0)
    return vpage("ロボットにも、<br>車検を作ろう", (
        bg("src/bg/ロボ検.jpg", 1920, -2333, 0, tp)
        + words([("ロボ検、", WHITE), ("開幕。", GOLD)], T(0, 2.6), tp, 600, size=150, gap=0.5)
        + paper(grp(card("hb", "ロボットにも車検を。名前は、ロボ検", T(0, 0.3), "ハコブくん", w=760, botsu=T(2, 0.3))
                    + card("mi", ink("誰が検査するの、それ", T(1, 0.2), 0.07), T(1, 0.05), "エイイチ", w=600, anim="wipe")
                    + card("fact", ink("屋内のロボットには、決まった手続きがない。改造した人が、安全を自分で説明する", T(3, 0.4), 0.035), T(3, 0.2), "だから", w=780), tp + 0.1), tp)
        + bigword("誰も<br>いない", T(2, 0.4), T(3, 0.0), 560, size=220)
        + shake_js([(T(2, 0.7), 0.3)])))


def short_kakin(T, S):
    tp = T(0, 3.2)
    return vpage("同じ体のまま、<br>お金で速くなる", (
        bg("src/bg/課金で解放.jpg", 1920, -1900, 0, tp)
        + words([("課金で", WHITE), ("解放。", GOLD)], T(0, 0.6), tp, 600, size=150, gap=0.5)
        + paper(grp(line("Model Y（2025年・米国）", tp + 0.3, size=34, weight=700)
                    + f'<div class="num" style="font-size:170px;line-height:1.05"><span style="font-size:80px">約$</span><span id="c2k">0</span></div>'
                    + line("部品は替えずに、ソフトの更新だけで速くなる", T(0, 4.6), size=32, weight=500)
                    + card("mi", ink("それ、ロボットでもやりそう", T(2, 0.2), 0.06), T(2, 0.05), "エイイチ", w=600, anim="wipe")
                    + f'<div class="row2" style="gap:24px">' + card("hb", "払いますか？", T(3, 0.05), "ハコブくん", w=300)
                    + card("mi", '<b style="font-size:54px">払います</b>', T(4, 0.0), "エイイチ", w=300, anim="wipe") + '</div>', tp + 0.1), tp)
        + counter_js("c2k", T(0, 4.0), T(0, 6.0), 2000, dec=0)
        + bigword("即答", T(5, 0.0), T(5, 99), 1300, size=260)
        + shake_js([(T(5, 0.3), 0.3)])))


# segs: (本編の文番号, 後ろの間[, 頭を切る秒, 字幕の文])
# big: 大きなハコブくんの出る間（end の文の頭で、左下の大きさへ）。kind: rise（下から飛び出す）/ hop（跳ねて喜ぶ）
SHORTS = [
    dict(no=1, name="改造してください", fn=short_kaizo, lead=0.5,
         segs=[(1, 0.7), (2, 0.4), (3, 0.6), (4, 1.2)],
         big=dict(kind="spin", end=1),
         face=[(-1, 0.0, 3.0, "joy"), (2, 0.0, 2.5, "trouble")],
         pose=[(-1, 0.0, 3), (1, 0.0, 1), (2, 0.0, 3), (3, 0.0, 2)], ei_rise=None,
         se=[(-1, 0.0, "se_whoosh", -10), (-1, 0.4, "se_rise", -8), (-1, 1.25, "se_pop", -4), (0, 0.2, "se_pop", -10), (0, 0.7, "se_pop", -10), (2, 0.2, "se_pop", -6), (3, 0.0, "se_whoosh", -12)]),
    dict(no=2, name="ロボ検", fn=short_robokan, lead=0.4,
         segs=[(39, 0.5), (40, 0.4), (41, 0.6), (42, 1.2, 0.0, "決まった手続きが｜ない分、改造した人が、｜安全を自分で｜説明することに｜なります。")],
         big=dict(kind="dash", end=1),
         face=[(-1, 0.0, 1.0, "surprise"), (-1, 1.0, 2.0, "trouble"), (0, 2.4, 2.5, "joy"), (2, 0.0, 2.5, "dot")],
         pose=[(-1, 0.0, 3), (1, 0.0, 2), (2, 0.0, 4), (3, 0.0, 1)], ei_rise=(1, 0.0),
         se=[(-1, 0.0, "se_whoosh", -6), (-1, 0.3, "se_backbeep", -10), (0, 2.6, "se_pop", -10), (0, 3.1, "se_pop", -8), (1, 0.0, "se_rise", -10), (2, 0.4, "se_pop", -6)]),
    dict(no=3, name="課金で解放", fn=short_kakin, lead=0.5,
         segs=[(58, 0.5, 0.0, "車のModel Yでは、2025年に、｜部品を替えずに、｜ソフトの更新だけで速くなる、｜約2,000ドルの｜有料の更新が出ています。"), (59, 0.4), (60, 0.3, 0.0, "それ、ロボットでも｜やりそうだね。"), (61, 0.2), (62, 0.3), (63, 1.2)],
         big=dict(kind="frenzy", end=1),
         face=[(-1, 0.0, 1.0, "surprise"), (-1, 1.0, 8.5, "joy"), (5, 0.0, 1.5, "wink")],
         pose=[(-1, 0.0, 3), (1, 0.0, 3), (2, 0.0, 1), (3, 0.0, 2), (4, 0.0, 4), (5, 0.0, 4)], ei_rise=None,
         se=[(-1, 0.0, "se_rise", -8), (0, 0.6, "se_pop", -10), (0, 1.1, "se_pop", -8), (5, 0.0, "se_pop", -6)] + [(-1, 0.45 + 0.37 * k, "se_pop", -18) for k in range(7)]
            + [(0, 4.0 + 0.4 * k, "se_tick", -16) for k in range(5)]),
]


def vpages10(text):
    """字幕の枚（1行10字・3行まで）。short_kit.wrap の折り方で"""
    out = []
    for part in text.split("｜"):
        if not part:
            continue
        ls = SK.wrap(part, SUB_MAXC)
        if ls is None:
            out.append(None); continue
        while ls:
            n = len(ls)
            k = 3 if n == 3 or n >= 5 else min(n, 2)
            out.append("\n".join(ls[:k])); ls = ls[k:]
    return out


def build(sh):
    idx_main = json.load(open("tts/index.json"))
    ei_main = set(K._ei_lines())
    lead = round(sh.get("lead", 0) * FPS)
    segs, t = [], lead
    for k, sg in enumerate(sh["segs"]):
        i, pause = sg[0], sg[1]
        trim = sg[2] if len(sg) > 2 else 0.0
        o = idx_main[i]
        dur = o["dur"] - trim
        frames = round((dur + pause) * FPS)
        segs.append(dict(k=k, i=i, trim=trim, text=sg[3] if len(sg) > 3 else o["text"], face=o.get("face", "base"),
                         start=t, speech=round(dur * FPS), frames=frames, ei=i in ei_main))
        t += frames
    total = t
    if total / FPS > 30.5:
        sys.exit(f"ショート{sh['no']} が {total / FPS:.1f}秒あります（30秒以内）")
    Tf = lambda k, off=0.0: (segs[k]["start"] / FPS if k >= 0 else 0.0) + off
    Sf = lambda k: segs[k]["speech"] / FPS
    html = sh["fn"](Tf, Sf)
    folder = f"ショート/{sh['no']:02d}_{sh['name']}"
    os.makedirs(folder + "/png", exist_ok=True)
    # --- 声 ---
    pcm = np.zeros(total * SR // FPS + SR, np.int16)
    for s in segs:
        with wave.open(f"tts/{s['i']:03d}.wav") as w:
            a = np.frombuffer(w.readframes(w.getnframes()), dtype=np.int16)
        a = a[int(s["trim"] * SR):][:s["frames"] * SR // FPS]
        if s["trim"]:
            a = (a * np.minimum(1, np.arange(len(a)) / (0.02 * SR))).astype(np.int16)   # 切った頭を短くなじませる
        s0 = s["start"] * SR // FPS
        pcm[s0:s0 + len(a)] = a
    pcm = pcm[:total * SR // FPS]
    # --- 字幕と紙の映像 ---
    from playwright.sync_api import sync_playwright
    SUBCSS = (f"*{{margin:0;padding:0}}html,body{{width:{W}px;height:{H}px;background:transparent}}svg{{position:absolute;left:0;top:0}}"
              f".t{{font-family:'Noto Sans CJK JP',sans-serif;font-size:{SK.SUB_FS}px;font-weight:800;fill:#fff;stroke:#0D0D0D;stroke-width:12px;stroke-linejoin:round;paint-order:stroke fill}}")
    subs = []
    with sync_playwright() as p:
        br = p.chromium.launch()
        pg = br.new_page(viewport={"width": W, "height": H})
        for s in segs:
            pages_ = vpages10(s["text"])
            n, tot, acc = len(pages_), sum(len(x or "") for x in pages_), 0
            for pk, ptxt in enumerate(pages_):
                if ptxt is None:
                    sys.exit(f"字幕が折れない: {s['text']}")
                svg = ''.join(f'<text x="{SUB_CX}" text-anchor="middle" class="t">{_html.escape(ln)}</text>' for ln in ptxt.split("\n"))
                pg.set_content(f'<!doctype html><html><head><meta charset="utf-8"><style>{SUBCSS}</style></head><body><svg width="{W}" height="{H}">{svg}</svg></body></html>')
                pg.evaluate(f"""() => {{ const ts=[...document.querySelectorAll('text.t')]; const n=ts.length; let fs={SK.SUB_FS};
                  for (const t of ts) {{ const w=t.getComputedTextLength(); if (w>{SK.SUB_MAXW}) fs=Math.min(fs, Math.floor({SK.SUB_FS}*{SK.SUB_MAXW}/w)); }}
                  ts.forEach((t,k)=>{{ t.style.fontSize=fs+'px'; t.setAttribute('y', Math.round({SUB_MID}+(k-(n-1)/2)*{SK.SUB_LH}+fs*0.36)); }}); }}""")
                path = f"{folder}/png/sub_{s['k']}_{pk}.png"
                pg.screenshot(path=path, omit_background=True)
                K.fill_holes(path)
                a = s["start"] + round(s["speech"] * acc / max(1, tot))
                acc += len(ptxt)
                b = s["start"] + (s["frames"] if pk == n - 1 else round(s["speech"] * acc / max(1, tot)))
                subs.append((a, b, K.sub_layer(path)))
        mp4 = f"{folder}/_paper.mp4"
        K.render_block(dict(name=sh["name"], html=html), total, mp4, pg)
        br.close()

    # --- ハコブくん: 全身。最初は大きく、本題で左下の大きさへ ---
    def full(hh):
        o = hk.Hakobu(mode="full")
        bb = o.body.getbbox()
        o.h = hh; o.w = round((bb[2] - bb[0]) * hh / (bb[3] - bb[1])); o._cache = {}
        return o
    hs, hb = full(HK_SMALL_H), full(HK_BIG_H)
    tline = [(s["start"], s["start"] + s["frames"], s["start"] + s["speech"], s["face"], not s["ei"]) for s in segs]
    if lead:
        tline = [(0, lead, lead, "base", False)] + tline
    mp = pcm.astype(np.float32) / 32768.0
    speak = np.zeros(total, bool); eispk = np.zeros(total, bool)
    for s in segs:
        a0, a1 = s["start"] * SR // FPS, (s["start"] + s["frames"]) * SR // FPS
        if s["ei"]:
            mp[a0:a1] = 0; eispk[s["start"]:s["start"] + s["speech"]] = True
        else:
            speak[s["start"]:s["start"] + s["speech"]] = True
    params = hs.animate(tline, mp)
    for k, a, d, f in sh["face"]:
        ex = hk.EXPR[f]
        for i in range(int(Tf(k, a) * FPS), min(total, int((Tf(k, a) + d) * FPS))):
            eyes, brows, mouth, ring, dy = params[i]
            if not speak[i]:
                mouth = ex["rest"]
            params[i] = (ex["eyes"], ex["brows"], mouth, ring, dy)
    t_end_big = Tf(sh["big"]["end"])
    SHR = 0.5   # 小さくなるのにかける秒

    def hk_big(t):
        """最初の大きなハコブくんの動き → (足もとの x, 足もと y, 角度, 横の伸び, 体の真ん中で回すか)
        2026-11-02 エイイチ「入りのハコブくんの動きをもっと大げさに、動画ごとにいろんな動きでコミカルに」"""
        kind = sh["big"]["kind"]
        if kind == "rise":
            k = 1.15 * np.sin(min(1, t / 0.45) * np.pi / 2) if t < 0.45 else 1.0 + 0.15 * np.exp(-(t - 0.45) * 6) * np.cos((t - 0.45) * 14)
            return 540 + 40 * np.sin(t * 1.1), 1990 + (1900 - 1990) * k + 14 * np.sin(t * 2.2), 6 * np.sin(t * 1.3), 1.0, False
        if kind == "hop":
            ph = (t * 1.9) % 1.0
            by = 1900 - 160 * 4 * ph * (1 - ph)
            if t < 0.3:
                by = 1920 + (by - 1920) * ease(t / 0.3)
            return 380 + 30 * np.sin(t * 2.0), by, 8 * np.sin(t * 3.8), 1.0, False
        if kind == "spin":     # 左から転がり込んで、高く跳んで1回転、どすんと着地してつぶれ、ぺこぺこお願いする
            if t < 0.35:
                u = ease(t / 0.35)
                return -400 + 900 * u, 1900, 25 * (1 - u), 1.0, False
            if t < 1.25:
                u = (t - 0.35) / 0.9
                return 500 + 60 * u, 1900 - 620 * 4 * u * (1 - u), 360 * ease(u), 1.0 - 0.12 * np.sin(u * np.pi), True
            if t < 1.6:
                u = (t - 1.25) / 0.35
                return 560, 1900, 0.0, 1.0 + 0.35 * np.exp(-u * 5) * np.cos(u * 12), False
            u = t - 1.6
            bow = max(0.0, np.sin(u * 7.5))   # 前に深くおじぎ（お願いします）
            return 560 + 10 * np.sin(u * 15), 1900 - 30 * abs(np.sin(u * 7.5)), 24 * bow, 1.0 + 0.05 * bow, False
        if kind == "dash":     # 右から猛ダッシュ → 行き過ぎて急ブレーキ → 汗をかいてガタガタ震える
            if t < 0.3:
                u = ease(t / 0.3)
                return 1500 - 1250 * u, 1900, -14 * u, 1.0 + 0.25 * u, False
            if t < 0.9:
                u = (t - 0.3) / 0.6
                return 250 + 290 * ease(u), 1900, -14 + 40 * np.exp(-u * 4) * np.sin(u * 14), 1.25 - 0.25 * ease(u), False
            u = t - 0.9
            shake = 1.0 if u < 1.5 else 0.35
            return 540 + 16 * shake * np.sin(u * 70), 1900 - 18 * abs(np.sin(u * 9)), 5 * shake * np.sin(u * 47), 1.0, False
        if kind == "frenzy":   # 下から飛び出して、お金の話に大興奮。左右に大きく揺れて、ぴょんぴょん跳ねる
            if t < 0.45:
                k = 1.2 * np.sin(min(1, t / 0.45) * np.pi / 2)
                return 540, 2050 - 150 * k, 0.0, 1.0, False
            u = t - 0.45
            hop = abs(np.sin(u * 8.5))
            return 540 + 90 * np.sin(u * 4.2), 1900 - 120 * hop, 22 * np.sin(u * 8.4), 1.0 + 0.15 * (1 - hop) ** 3, False
        raise ValueError(kind)

    def hk_place(t):
        """→ (高さ, 足もとの中心 x, 足もと y, 角度, 横の伸び, 体の真ん中で回すか)"""
        sx, sy = HK_X + hs.w / 2, FOOT
        if t >= t_end_big + SHR:
            return HK_SMALL_H, sx, sy, 0.0, 1.0, False
        bx, by, rot, st, ctr = hk_big(t)
        if t < t_end_big:
            return HK_BIG_H, bx, by, rot, st, ctr
        u = ease((t - t_end_big) / SHR)
        return HK_BIG_H + (HK_SMALL_H - HK_BIG_H) * u, bx + (sx - bx) * u, by + (sy - by) * u, rot * (1 - u), 1.0 + (st - 1.0) * (1 - u), False

    # --- エイイチ（全身・2倍） ---
    sprites = {}
    for k_, nm in EI_POSES.items():
        im = Image.open(f"src/ei/エイイチ_全身_{nm}_170px_v2.png").convert("RGBA")
        im = im.resize((round(im.width * EI_SCALE), round(im.height * EI_SCALE)), Image.NEAREST)
        # 暗い背景に服が沈むので、うすい光の縁を付ける（本編の暗い場面と同じ）
        from PIL import ImageFilter
        pad = 20
        g = Image.new("L", (im.width + 2 * pad, im.height + 2 * pad), 0)
        g.paste(im.split()[3], (pad, pad))
        g = g.filter(ImageFilter.GaussianBlur(7)).point(lambda v: int(min(255, v * 1.6) * 0.45))
        lay = Image.merge("RGBA", (Image.new("L", g.size, 235), Image.new("L", g.size, 232), Image.new("L", g.size, 225), g))
        lay.alpha_composite(im, (pad, pad))
        sprites[k_] = np.asarray(lay.crop((pad, 0, pad + im.width, pad + im.height))).astype(np.float32)
    ei_pcm = pcm.astype(np.float32) / 32768.0
    hop = SR // FPS
    rms = np.array([np.sqrt(np.mean(ei_pcm[i * hop:(i + 1) * hop] ** 2)) if eispk[i] else 0.0 for i in range(total)])
    ref = np.percentile(rms[rms > 0.01], 85) if (rms > 0.01).any() else 1.0
    bob = np.zeros(total, int)
    for i in range(0, total, 3):
        bob[i:i + 3] = -EI_SCALE if rms[i:i + 3].mean() > 0.45 * ref else 0
    poses = sorted((Tf(k, a), p_) for k, a, p_ in sh["pose"])

    def ei_dy(t):
        """ei_rise: その時刻まで画面の下に隠れていて、下から出てくる"""
        if not sh.get("ei_rise"):
            return 0
        t0 = Tf(*sh["ei_rise"])
        if t < t0:
            return 760
        return int(760 * (1 - ease((t - t0) / 0.35)))

    # --- 音 ---
    voice = pcm.astype(np.float32) / 32768.0
    ref_v = np.percentile(np.abs(voice[voice != 0]), 90) if (voice != 0).any() else 0.1
    raw = subprocess.run(["ffmpeg", "-v", "error", "-i", "sound/bgm_明るいB_2.mp3", "-ac", "1", "-ar", str(SR), "-f", "s16le", "-"], capture_output=True, check=True).stdout
    bgm = np.frombuffer(raw, np.int16).astype(np.float32) / 32768.0
    bgm = np.tile(bgm, int(np.ceil(len(voice) / len(bgm))) + 1)[:len(voice)]
    bgm *= ref_v / (np.percentile(np.abs(bgm), 90) + 1e-6) * 10 ** (-24 / 20)
    fade = np.ones(len(voice)); nfo = int(1.0 * SR); fade[-nfo:] = np.linspace(1, 0, nfo)
    mix = voice + bgm * fade
    for k, off, name, lv in sh["se"]:
        raw = subprocess.run(["ffmpeg", "-v", "error", "-i", os.path.join(K.BASE, "sound", name + ".wav"), "-ac", "1", "-ar", str(SR), "-f", "s16le", "-"], capture_output=True, check=True).stdout
        se = np.frombuffer(raw, np.int16).astype(np.float32) / 32768.0
        se *= ref_v / (np.abs(se).max() + 1e-6) * 10 ** (lv / 20)
        s0 = int(Tf(k, off) * SR)
        n = min(len(se), len(mix) - s0)
        if n > 0:
            mix[s0:s0 + n] += se[:n]
    mix *= 0.98 / max(0.98, np.abs(mix).max())
    K.write_wav(f"{folder}/_mix.wav", (mix * 32767).astype(np.int16))

    # --- 重ねる ---
    out = f"{folder}/ショート{sh['no']}_{sh['name']}_版2.mp4"
    enc = subprocess.Popen(["ffmpeg", "-v", "error", "-y", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{W}x{H}", "-r", str(FPS), "-i", "-",
                            "-i", f"{folder}/_mix.wav", "-map", "0:v", "-map", "1:a", "-c:v", "libx264", "-preset", "medium", "-crf", "20", "-pix_fmt", "yuv420p",
                            "-af", "loudnorm=I=-14:TP=-1.5:LRA=11", "-c:a", "aac", "-b:a", "160k", "-ar", "48000", "-movflags", "+faststart",
                            "-frames:v", str(total), out], stdin=subprocess.PIPE)
    dec = subprocess.Popen(["ffmpeg", "-v", "error", "-i", mp4, "-f", "rawvideo", "-pix_fmt", "rgb24", "-"], stdout=subprocess.PIPE)
    for f in range(total):
        t = f / FPS
        bgf = np.frombuffer(dec.stdout.read(W * H * 3), np.uint8).reshape(H, W, 3).copy()
        pose = 1
        for a, p_ in poses:
            if t >= a:
                pose = p_
        spr = sprites[pose]
        blend(bgf, spr, W - spr.shape[1] - 12, FOOT - spr.shape[0] + bob[f] + ei_dy(t))   # エイイチは右端に寄せる
        hh_ = sh.get("hk_hide")   # 絵にハコブくんが描いてある間は、重ねない（2026-11-02 どこまでいじっていい）
        if hh_ and Tf(*hh_[0]) <= t < Tf(*hh_[1]):
            for a, b, (sl, sx_, sy_) in subs:
                if a <= f < b:
                    blend(bgf, sl, sx_, sy_)
            enc.stdin.write(bgf.tobytes())
            continue
        size, cx, fy, rot, st, ctr = hk_place(t)
        img = (hb if size > HK_SMALL_H + 1 else hs).frame(params[f])
        if abs(size - img.height) > 1 or abs(st - 1.0) > 0.01:
            img = img.resize((round(img.width * size / img.height * st), round(size / st)), Image.LANCZOS)
        x, y = int(cx - img.width / 2), int(fy - img.height)
        if abs(rot) > 0.2:
            img, (ox, oy) = rotate_about(img, rot, (img.width / 2, img.height / 2 if ctr else img.height))
            x, y = x + ox, y + oy
        blend(bgf, np.asarray(img).astype(np.float32), x, y)
        for a, b, (sl, sx_, sy_) in subs:
            if a <= f < b:
                blend(bgf, sl, sx_, sy_)
        enc.stdin.write(bgf.tobytes())
    dec.stdout.close(); dec.wait()
    enc.stdin.close(); enc.wait()
    print(f"{out}  {total / FPS:.1f}秒")
    return out


if __name__ == "__main__":
    want = [int(a) for a in sys.argv[1:]] or [s["no"] for s in SHORTS]
    for sh in SHORTS:
        if sh["no"] in want:
            build(sh)
```

### ショートの書き出しの出力（python3 kabe_short.py）

```text
$ python3 kabe_short.py
  改造してください: 527コマ（撮影 211）
ショート/01_改造してください/ショート1_改造してください_版1.mp4  17.6秒
字幕が折れない: 決まった手続きがない分、改造した人が、安全を自分で説明することになります。

$ python3 kabe_short.py 2 3
字幕が折れない: 決まった手続きがない分、｜改造した人が、｜安全を自分で説明することになります。

$ python3 kabe_short.py 2 3
  ロボ検: 529コマ（撮影 173）
ショート/02_ロボ検/ショート2_ロボ検_版1.mp4  17.6秒
字幕が折れない: それ、ロボットでもやりそうだね。

$ python3 kabe_short.py 3
  課金で解放: 617コマ（撮影 208）
ショート/03_課金で解放/ショート3_課金で解放_版1.mp4  20.6秒

$ python3 kabe_short.py 1 2     # 背景の絵のハコブくんを隠す hk_hide を足した版1
ショート/01_改造してください/ショート1_改造してください_版1.mp4  17.6秒
ショート/02_ロボ検/ショート2_ロボ検_版1.mp4  17.6秒

$ python3 kabe_short.py         # 版2：入りの動きを spin・dash・frenzy に
ショート/01_改造してください/ショート1_改造してください_版2.mp4  17.6秒
ショート/02_ロボ検/ショート2_ロボ検_版2.mp4  17.6秒
ショート/03_課金で解放/ショート3_課金で解放_版2.mp4  20.6秒
```

### ショート_タイトルと説明文.txt

```text
ショートのタイトルと説明文（本編「配膳ロボットの改造はどこまでOK？ 責任・契約・追加学習を考えた」から作った3本・版1）
関連動画には本編を設定してください。

━━━━━━━━━━━━━━━━
ショート1_改造してください（17.6秒）
タイトル：
「僕を改造してください」→ 却下。責任は誰に？ #Shorts
説明文：
ハコブくんが自分を改造してほしいと言い出した。でも、事故が起きたら誰の責任？　EU でも日本でも、条件によっては、改造した人がメーカーと同じ責任を負う側に入ります。法律の話は調べた範囲の理解で、専門家の判断ではありません。
自分で改造するハコブくんの絵は生成AI（Gemini）で作成しました。声は AI 音声です（エイイチの声も AI 音声で、本人の声ではありません）。
本編：配膳ロボットの改造はどこまでOK？ 責任・契約・追加学習を考えた
#フィジカルAI #配膳ロボット #ロボット

━━━━━━━━━━━━━━━━
ショート2_ロボ検（17.6秒）
タイトル：
ロボットにも車検を。名前は「ロボ検」 #Shorts
説明文：
車には「ここまで変えたら、この検査」という受け皿の制度がある。屋内のロボットには、まだない。だから改造した人が、安全を自分で説明することになります。……ロボ検、誰が検査するの？
ロボ検の絵は生成AI（Gemini）で作成しました。声は AI 音声です（エイイチの声も AI 音声で、本人の声ではありません）。
本編：配膳ロボットの改造はどこまでOK？ 責任・契約・追加学習を考えた
#フィジカルAI #配膳ロボット #ロボット

━━━━━━━━━━━━━━━━
ショート3_課金で解放（20.6秒）
タイトル：
同じ体のまま、お金で速くなる車 #Shorts
説明文：
Model Y では、2025年に、部品を替えずにソフトの更新だけで速くなる、約2,000ドルの有料の更新が出ています（米国の価格）。ロボットでも、やりそう。……払いますか？
出どころ：EVANNEX（2025年6月2日）。宝箱の絵は生成AI（Gemini）で作成しました。声は AI 音声です（エイイチの声も AI 音声で、本人の声ではありません）。
本編：配膳ロボットの改造はどこまでOK？ 責任・契約・追加学習を考えた
#フィジカルAI #配膳ロボット #ロボット
```

### 投稿文_X_LinkedIn.txt（YouTube に上げたあとの投稿文）

```text
■ X（URL込み140字以内。【URL】を YouTube の動画の URL に置きかえる）
配膳ロボットを自分の店に合わせて改造したい。でも改造すると、責任は改造した人へ。ソフトは買っても借り物。触っていいのはどこまでか、ハコブくんと壁打ちした考察回です。
【URL】
#R2R2R #PixelsToMotors

■ LinkedIn（動画は LinkedIn に直接アップロードする）
"Please remodel me." Rejected.
My robot narrator asked to have its speed limit removed. My first question: if there is an accident, who is responsible?

In the EU and in Japan, under certain conditions, the person who modifies a machine can end up carrying the same responsibility as the maker.
And even if you buy a robot, you usually do not own the software. You only have the right to use it.

In this video, Hakobu and I think through how far you can change a robot you bought from a maker:
- Five levels of touching a robot, and the line between level 3 and level 4 where law, warranty and maintenance split
- Why "buy it and customize it" works for cars but not for indoor robots (no inspection system to fall back on)
- Opening more external connections opens more holes
- Where this goes next: not rewriting the machine, but teaching it motions that fit your own site

This is research notes, not legal advice.

「僕を、改造してください」――却下。
ハコブくんが速さの上限を外したいと言ったとき、最初に浮かんだのは「事故が起きたら、誰の責任？」でした。
EU でも日本でも、条件によっては、改造した人がメーカーと同じ責任を負う側に入ります。しかも、ロボットを買っても、ソフトはふつう買っていません。使う権利を借りているだけです。
動画では、ハコブくんと一緒に、
・触り方の5段階と、3段目と4段目の間の切れ目（法律・保証・保守がここで分かれる）
・車は買えば改造できるのに、屋内のロボットにはそれを受ける制度がないこと
・外からつなげる口を増やすほど、穴も増えること
・改造の行き先は、中身の書き換えではなく、自分の現場に合う動きを学ばせること
を考えました。法律の話は調べた範囲の理解で、専門家の判断ではありません。考えた道すじの全文はサイトの記事にあります。

YouTube のほかの動画: https://www.youtube.com/@PixelstoMotors

（コメント欄の補足）
出典：YouTube 版 【URL】／記事 https://pixelstomotors.com/record/service-robot-modding
```
