# 4コママンガ『ハコブくん』を画像生成AIで描く費用 ― 5話で約590円、1話約120円

- 記録: LOG508（ひとり研究所の裏側）
- 公開: 2026/11/14
- 記事のページ: https://pixelstomotors.com/record/hakobu-4koma-cost-per-episode
- サイト: Pixels to Motors（ADAPT DESIGN / 島津 栄一） https://pixelstomotors.com/
- 要約: 4コママンガ『ハコブくん』第148〜152話を、絵・声・ショート・英語版・サイト公開・SNS予約まで出した費用は約590円（1話約120円）。Gemini の絵が約550円で、バッチは1枚約5.6円、ふつうの頼み方は約11円だった。

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

---

**4コママンガ『ハコブくん』の第148〜152話の5話を、Google の Gemini で絵を描き、ElevenLabs の声でショート動画にし、英語版とサイトへの公開、SNS の予約まで出した費用は、使った量から計算して約590円、1話あたり約120円でした。** そのうち約550円が Gemini の絵の代金で、残りは声が約13円、X の投稿の API が約24円です。5話のマンガは [pixelstomotors.com/manga/ep148](https://pixelstomotors.com/manga/ep148) から読めます。

費用を1回の頼みごとに出したので、次のことが分かりました。

- 何にお金がかかっているか：絵の代金の4割（224円）は最初の下書き40枚で、残りの6割（326円）は描き直しの24回。描き直しが減れば、そのまま安くなる
- 頼み方の差：バッチ（まとめて頼む半額の頼み方）は1枚約5.6円、その場で頼むふつうの頼み方は約11円。下書きをバッチにまとめた分、40枚で約220円浮いた
- 前の5話との差：第143〜147話（LOG468）は、Gemini に描かせた絵が120枚・見積もりで約1,100円、エイイチの指摘が5回だった。今回は描かせた絵が69枚・約550円、指摘は1回。依頼から SNS の予約を入れ終えるまで約1時間15分だった

**計算で1つ気づいたのは、1回の頼みで絵が6枚返ってきて、その1回だけで約65円かかっていたことです。** ふつうは1回で1枚（約11円）なので、この1回で5回分を払っています。描く道具は最後の1枚だけを保存するので、ファイルを見ても気づけませんでした。返事の使用量（出力の画像のトークン数）を1回ずつ足したので見つかりました。

もう1つは、Google AI Studio の月の支払い上限です。11月は自動の足し込みで800円ずつ6回、4,800円が入っていて、上限の5,000円に届いていました。そのため、表紙を作るための Gemini の編集（吹き出しを消す）は5回とも断られ、表紙の吹き出しはプログラムで消しました。断られた頼みには費用がかかっていません。

やったことの全体は次のとおりです。

| やったこと | 結果 | Gemini の費用 |
|---|---|---|
| 第148〜152話のプロットから、5話の構図とセリフの指示（spec）を書いた | 20コマの指示 | ― |
| 下書きをバッチで40枚頼んだ | 4分半で全部返った | 224円 |
| 1コマずつの確認で描き直した | 第150話 P1（字の間違い）・第151話 P4（隣のコマと同じ構図） | 99円 |
| 別のエージェントに全コマを見てもらい、描き直した | 誰の手か分からない手・ハコブくんが話し相手でなく読者を向く、の6コマ | 135円 |
| エイイチの指摘（ハコブくんが細すぎる）で描き直した | 4コマ | 91円 |
| ショート5本（冒頭の動き・声・書き出し） | 21〜29秒 | 声 約13円 |
| 表紙・英語版を作り、サイトに10ページを公開した | 日本語と英語の5話ずつ | 0円 |
| SNS を予約した | 日本語の X 5本・英語の X 5本 | X 約24円（投稿のときに引かれる） |

![第148〜152話の確定版。上から1話ずつ、左から1〜4コマ目](https://pixelstomotors.com/media/LOG508_01_final_sheet.jpg)

数字について断っておきます。Gemini の費用は、返事に入っている使用量（トークン数）と Google の料金のページの単価から出した計算です（請求書の明細ではありません）。声の費用は、ElevenLabs の月額の料金を1か月に使える文字数で割った単価から出しています。X の費用は、まだ投稿されていない予約の分です。

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


## 作業ログ ― 第148〜152話のプロットから、構図とセリフの指示を書いた

### 使ったプロット（plots_134_158_直した版.tsv）

進捗管理表（Google スプレッドシート「4コママンガプロット」）の未着手の先頭5話を使いました。プロットは、病院のロボットの報道と研究を調べて作り、codex（別の AI）に疑ってもらって直した表です（作業フォルダ `記事/ハコブくん_新プロット_20261031/`）。

| 話 | 題 | 登場人物 | もとにした調べ |
|---|---|---|---|
| 148 | 積む人がいない | 大城、寺島、白石、ハコブくん | ロボットは人が積まないと仕事が始まらず、急な対応の多い病棟では動けない働き手と見られた（[米国の病院の研究の紹介・hinnovic](https://www.hinnovic.org/post/a-robot-as-a-co-worker)） |
| 149 | 呼ばれなくなった | 寺島、白石、ハコブくん | 厨房スタッフ16人のうち8人が、ロボットを当てにできないとして自分でやっていた（[デンマークの病院の研究](https://vbn.aau.dk/ws/files/461098840/SHTI_286_SHTI210639.pdf)） |
| 150 | 7つのはずが28 | 大城、白石、ハコブくん | 管理側は7段階と説明したが、実際の運用は28段階だった（[ACM Interactions](https://interactions.acm.org/archive/view/march-april-2025/the-invisible-work-of-human-robot-collaboration-when-streamlined-processes-meet-the-complexity-of-real-world-practices)） |
| 151 | 着いたのに | 大城、田中先生、ハコブくん | 救急外来の薬の搬送ロボットで、到着を看護師と薬剤師に知らせる仕組みを試作と試験で作り直した（[チャンギ総合病院](https://www.cgh.com.sg/about-cgh/overview/cgh90thanniversary/care-grows-here/engineering-care-together)） |
| 152 | 間に合うかどうか | 寺島、白石、ハコブくん | ロボットが階をまたぐと迷うため、看護師が採血の検体を任せなくなった（[Proof News](https://www.proofnews.org/moxi/)） |

登場人物はプロットより少なく絞りました。第151話は白石さんを出さず、第152話は検査室の人を電話の声だけにしています。ハコブくんの画面には字を出さない決まりなので、プロットで「画面に『積ミ込ミ待チ』」「画面に『到着シマシタ』」と書かれていた所は、吹き出しのセリフにしました。

### 5話で使う共通の文（_common_148_152.py）

ハコブくんの大きさを体の部位で書く文、ドロワーを閉じる文、向きを書く文をまとめました。`TURN` は別のエージェントの確認のあと、`WIDE` はエイイチの指摘のあとに足したものです（下の節）。

```python
# 第148〜152話で使う共通の文（2026-11-14）
import os
from importlib.machinery import SourceFileLoader as _L
F = _L("_face", os.path.expanduser("~/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/縦長/_face.py")).load_module().face
def SIZE(name, part):
    return "SIZE: Hakobukun's top edge reaches only %s. %s's chest, shoulders and head are clearly ABOVE the robot. Hakobukun is NOT as tall as %s chest" % (part, name, "her" if name in ("Oshiro", "Terashima", "Shiraishi") else "his")
PIT = "the pit of {n}'s stomach"
LOWCHEST = "the lower edge of {n}'s chest — still clearly below her shoulders"
CLOSED = ("HAKOBUKUN DRAWER — CLOSED:", ["Hakobukun's drawer is fully CLOSED, exactly as on the reference sheet. Its body has NO window, NO transparent panel, NO opening — nothing inside the robot is visible"])

# 2026-11-14 チェック①（別のエージェント）：ハコブくんが話し相手でなく読者を向くコマが5つ → 向きを見える形でさらに書く
def TURN(side, who):
    away = "LEFT" if side == "right" else "RIGHT"
    return ("HAKOBUKUN IS TURNED TOWARD %s — SEEN FROM ITS %s SIDE:" % (who.upper(), away), [
        "we see Hakobukun mostly from its %s side: the flat side panel with the round ring is LARGE and faces the viewer" % away.lower(),
        "its screen face is turned toward %s and is seen at a STEEP angle, as a narrow slanted shape at the %s edge of its silhouette" % (who, side.upper()),
        "the white oval eyes on the screen look toward %s, NOT at the viewer" % who])

# 2026-11-14 エイイチ「ハコブくん細すぎ。無理に入れ込まないで、人は多少脇にはみだしてもいい」→ ハコブくんの幅を画像の幅で決め、人は左右の端で切れてよいと書く
def WIDE(x0, x1):
    return ("HAKOBUKUN WIDTH — WIDE AND STOUT, NEVER SQUEEZED:", [
        "Hakobukun's body spans x %d–%d%% of the image width — about one third of the picture. Its width is its height divided by 1.7, exactly as on the reference sheet" % (x0, x1),
        "do NOT slim or narrow Hakobukun to make room for the people. Give the robot its full width FIRST, then place the people",
        "the people may be cut by the left or right edge of the picture (part of the shoulder or arm outside the frame) — that is fine. Squeezing the robot is NOT"])
```

### 第148話 spec.py

spec.py は、1話の4コマの構図・カメラ・セリフ・大きさを書くファイルです。`vbuild.py`（LOG448 までに作った道具）がこれを読み、キャラクターの参考の絵と決まりの文を足して Gemini への頼み文を組みます。下は、描き直しを全部終えた時点の spec.py で、直したときの上書きを日付と理由を添えて足しています。

```python
EP, TITLE = 148, "積む人がいない"
# 2026-11-14 カメラワーク：P1 ハコブくん目線100cm・全身、リネン庫の前でドロワーを開けて待つ（横に袋を積んだカート）
# P2 人の目線・横から、手前を走り抜ける大城さんと寺島さん（ハコブくんは奥に小さく）／P3 低い90cm・斜め前、同じ床の寺島さんとハコブくん（時計）
# P4 白石さんの肩なめ150cm、表を持つ白石さんと、奥で別の仕事へ向かうハコブくん
import os
from importlib.machinery import SourceFileLoader as _L
C = _L("_c", os.path.expanduser("~/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/縦長/_common_148_152.py")).load_module()
CART = "a plain wheeled linen cart piled high with four or five tied white laundry bags of used linen. NO text, NO labels"
DOOR = "a plain closed door of a linen room behind, with a plain door handle. NO sign, NO text"
PANELS = [
 dict(chars=["ハコブ"], mouth="NORMAL", drawer=True, no_yline=True, prop=("LINEN CART AND DOOR", [CART + ", standing just to the right of Hakobukun", DOOR]),
      hscale_text=["Hakobukun stands at the left-centre, full body, three-quarter view facing camera-right toward the cart, its drawer pulled fully OPEN and EMPTY: its top at about 34% and wheels at about 88% of the image height, x 12–48%",
                   "the linen cart stands at the right, x 54–90%, its top at about 40% and wheels at about 88% of the image height — about as tall as Hakobukun"],
      camera=["SHOT SIZE: full body of Hakobukun with the cart", "CAMERA POSITION: front-left, three-quarter", "CAMERA HEIGHT: 100 cm (Hakobukun's eye level)"],
      band="3% and 22%",
      comp=["Hakobukun waiting patiently, cheerful, its empty drawer open toward the cart",
            "the bubble 「積ミ込ミヲ、オ待チシテイマス」 at the upper centre-left, its tail down to Hakobukun"],
      bubbles=[("Hakobukun", "積ミ込ミヲ、オ待チシテイマス", ["積ミ込ミヲ、", "オ待チシテイマス"])],
      extra=[("KATAKANA CHECK:", ["every kana in 積ミ込ミヲ、オ待チシテイマス is KATAKANA: ミ, ミ, ヲ, オ, チ, シ, テ, イ, マ, ス — NOT hiragana"])],
      scene="Evening on the ward. In front of the linen room, Hakobukun waits with its drawer open, next to a cart piled with bags of used linen. Hakobukun has no arms: someone has to load the bags."),
 dict(chars=["大城", "寺島", "ハコブ"], mouth="NORMAL", no_yline=True, drawer=True, prop=("LINEN CART", [CART + ", tiny in the far background next to the robot"]),
      hscale_text=["Oshiro and Terashima are LARGE in the near foreground, knee-up, RUNNING from left to right across the picture, side view turned 20° toward the camera: Oshiro at x 44–80%, Terashima just behind her at x 14–46%, both heads at about 26–30% of the image height",
                   "far beyond them at the upper-left background, Hakobukun is SMALL, three-quarter view, still waiting with its drawer open beside the tiny cart: its top at about 44% and wheels at about 60% of the image height, x 6–20%"],
      camera=["SHOT SIZE: knee-up of the two running nurses; Hakobukun tiny in the background", "CAMERA POSITION: from the side of the corridor", "CAMERA HEIGHT: 150 cm"],
      band="3% and 20%",
      comp=["both nurses run hard with serious faces; Terashima's round glasses glint",
            "two bubbles: 「302号室、急変です！」 at the upper left, tail down to Terashima; 「すぐ行く！」 at the upper right, tail down to Oshiro"],
      bubbles=[("Nurse Terashima", "302号室、急変です！", ["302号室、", "急変です！"]), ("Nurse Oshiro", "すぐ行く！", ["すぐ行く！"])],
      extra=[("EFFECT:", ["horizontal speed lines behind the running nurses. NO letters"])],
      scene="Down the corridor, a patient suddenly gets worse. Oshiro and Terashima run past. Nobody comes to the linen room; Hakobukun keeps waiting far behind."),
 dict(chars=["寺島", "ハコブ"], mouth="STOPPED", full=["寺島"], drawer=True, prop=("LINEN CART AND CLOCK", [CART + ", just behind Hakobukun at the left edge, x 0–14%", "a plain round wall clock high on the upper right, with two hands and NO numerals, NO text"]),
      camera=["SHOT SIZE: full-body two-shot", "CAMERA POSITION: front-right, three-quarter", "CAMERA HEIGHT: 90 cm, looking slightly up"],
      band="3% and 20%",
      comp=["Hakobukun is centred at about 32% of the image width, still in the same place with its drawer open and EMPTY, a few drifting dots of silence above its screen",
            "Terashima is centred at about 66% of the image width, body turned 40° toward the robot, tired after the emergency, one hand raised apologetically",
            C.SIZE("Terashima", C.LOWCHEST.format(n="Terashima")),
            "two bubbles: 「誰モ、積ミマセンデシタ」 at the upper left, tail down to Hakobukun; 「ごめん、今日はそれどころじゃなくて…」 at the upper right, tail down to Terashima"],
      bubbles=[("Hakobukun", "誰モ、積ミマセンデシタ", ["誰モ、", "積ミマセンデシタ"]), ("Nurse Terashima", "ごめん、今日はそれどころじゃなくて…", ["ごめん、今日は", "それどころじゃなくて…"])],
      extra=[C.F("right", "Terashima"), ("KATAKANA CHECK:", ["in 積ミマセンデシタ the ミ, マ, セ, ン, デ, シ, タ are KATAKANA"])],
      scene="An hour later. Hakobukun is still in the same place, its drawer still empty. Terashima comes back from the emergency and apologises."),
 dict(chars=["白石", "ハコブ"], mouth="HAPPY", no_yline=True,
      hscale_text=["Shiraishi is in the near foreground at the RIGHT, seen from her left rear three-quarter, chest-up, cut by the right and bottom edges: x 56–100%, her head at about 30% of the image height",
                   "she holds up a plain sheet with a simple grid timetable (boxes and tick marks only, NO letters, NO numbers) at x 40–70%, y 44–66%",
                   "beyond her at the left, about 3 m away, Hakobukun rolls off to its next job, three-quarter view moving toward camera-left, drawer CLOSED: its top at about 46% and wheels at about 78% of the image height, x 8–36%"],
      camera=["SHOT SIZE: over-the-shoulder — Shiraishi in the foreground, Hakobukun full body beyond", "CAMERA POSITION: behind Shiraishi's left shoulder", "CAMERA HEIGHT: 150 cm"],
      band="3% and 22%",
      comp=["Shiraishi nodding, satisfied",
            "two bubbles: 「積ム人ガイナイ時ハ、後デ来マス」 at the upper left, tail down to Hakobukun; 「積む人も、予定に入れなきゃね」 at the upper right, tail down to Shiraishi"],
      bubbles=[("Hakobukun", "積ム人ガイナイ時ハ、後デ来マス", ["積ム人ガイナイ時ハ、", "後デ来マス"]), ("Engineer Shiraishi", "積む人も、予定に入れなきゃね", ["積む人も、", "予定に入れなきゃね"])],
      extra=[C.CLOSED, ("EFFECT:", ["a few light motion lines behind Hakobukun as it rolls away. NO letters"]), ("KATAKANA CHECK:", ["in 積ム人ガイナイ時ハ、後デ来マス the ム, ガ, イ, ナ, イ, ハ, デ, マ, ス are KATAKANA"])],
      scene="Shiraishi makes a timetable of loading times and who loads. If the loader's hands are full, Hakobukun does not wait forever; it goes to another job first and comes back later."),
]

# 2026-11-14 バッチの下書き（1コマ2枚）と描き直しから、チェック①で選んだ
PICK = {1: '第148話_P1_20261114_004803b2.jpg', 2: '第148話_P2_20261114_004803b1.jpg', 3: '第148話_P3_20261114_004803b1.jpg', 4: '第148話_P4_20261114_004803b1.jpg'}

# 2026-11-14 チェック①（別のエージェント）：P3 のハコブくんが寺島さんでなく読者を向く・カートが消え「1時間後」が分からない → 向きを書き足し、カートと時計を必ず描く
PANELS[2]["extra"] = [C.TURN("right", "Terashima")] + PANELS[2]["extra"]
PANELS[2]["comp"].append("the linen cart with the bags is clearly visible at the far left edge behind Hakobukun, and the plain wall clock is clearly visible at the upper right — both must be drawn")
PANELS[2]["comp"].append("the text in both bubbles runs HORIZONTALLY")

# 2026-11-14 描き直しから選び直した（149 P3 は描き直し2枚とも形・大きさが崩れたので前の絵のまま）
PICK[3] = "第148話_P3_20261114_005443.jpg"
```

### 第149話 spec.py

```python
EP, TITLE = 149, "呼ばれなくなった"
# 2026-11-14 カメラワーク：P1 白石さんの肩越しにタブレットの画面（棒が右へ下がる）150cm／P2 廊下を歩く寺島さんの膝上・斜め前140cm、薬の袋を自分で持つ
# P3 低い90cm・斜め前、部屋の隅の充電台のハコブくんと、横に立つ白石さん（同じ床）／P4 斜め前150cm・3人の全身、2本の棒を見せる白石さんと寺島さん
import os
from importlib.machinery import SourceFileLoader as _L
C = _L("_c", os.path.expanduser("~/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/縦長/_common_148_152.py")).load_module()
TAB = "a plain tablet"
PANELS = [
 dict(chars=["白石"], no_yline=True, prop=("TABLET", [TAB + " held in Shiraishi's two hands; its screen shows a simple bar chart of about seven bars that drop to almost nothing on the right. NO letters, NO numbers, NO axis labels"]),
      hscale_text=["Shiraishi is in the near foreground at the LEFT, seen over her right shoulder from behind, chest-up, cut by the left and bottom edges: x 0–52%, her head at about 30% of the image height",
                   "the tablet screen is LARGE and clearly readable at the right-centre: x 40–92%, y 40–76%"],
      camera=["SHOT SIZE: over-the-shoulder close-up on the tablet", "CAMERA POSITION: behind Shiraishi's right shoulder", "CAMERA HEIGHT: 150 cm"],
      band="3% and 22%",
      comp=["Shiraishi tilting her head, puzzled",
            "the bubble 「あれ、ここ数日、配送の依頼がほとんどない」 at the upper right, its tail down to Shiraishi"],
      bubbles=[("Engineer Shiraishi", "あれ、ここ数日、配送の依頼がほとんどない", ["あれ、ここ数日、", "配送の依頼が", "ほとんどない"])],
      scene="Morning. Shiraishi checks Hakobukun's records on a tablet: for the last few days, there have been almost no delivery requests."),
 dict(chars=["寺島"], no_yline=True, prop=("MEDICINE BAG", ["a plain small paper medicine bag carried in Terashima's hand. NO text"]),
      hscale_text=["Terashima is at the centre-right, knee-up, walking briskly toward camera-left, body turned 30° toward the camera: x 30–80%, her head at about 22% of the image height, her legs cut by the bottom edge at the knees"],
      camera=["SHOT SIZE: knee-up of Terashima walking", "CAMERA POSITION: front-left, three-quarter", "CAMERA HEIGHT: 140 cm"],
      band="3% and 18%",
      comp=["Terashima matter-of-fact, carrying the empty paper bag, glancing back over her shoulder to answer someone behind her (off-screen right)",
            "the bubble 「ハコブくん？この前2回遅れたから、自分で行ったほうが確実なの」 at the upper left, LARGE, its tail down to Terashima"],
      bubbles=[("Nurse Terashima", "ハコブくん？この前2回遅れたから、自分で行ったほうが確実なの", ["ハコブくん？", "この前2回遅れたから、", "自分で行ったほうが", "確実なの"])],
      extra=[("EFFECT:", ["a few light motion lines behind her as she walks. NO letters"])],
      scene="On the ward, Terashima is heading to the pharmacy herself. Asked about Hakobukun, she says it was late twice, so going herself is more reliable."),
 dict(chars=["白石", "ハコブ"], mouth="TROUBLED", full=["白石"], prop=("CHARGING DOCK", ["a plain low charging dock on the floor against the wall corner, Hakobukun parked on it. NO text, NO lights with letters"]),
      camera=["SHOT SIZE: full-body two-shot", "CAMERA POSITION: front-right, three-quarter", "CAMERA HEIGHT: 90 cm, looking slightly up"],
      band="3% and 20%",
      comp=["Hakobukun is centred at about 32% of the image width, parked on the charging dock in a corner, drifting dots of loneliness above its screen",
            "Shiraishi is centred at about 66% of the image width, body turned 40° toward the robot, one hand on her chin, thinking hard",
            C.SIZE("Shiraishi", C.LOWCHEST.format(n="Shiraishi")),
            "two bubbles: 「ワタシハ、呼バレテイマセン」 at the upper left, tail down to Hakobukun; 「故障の報告はない。ただ、使われなくなってた」 at the upper right, tail down to Shiraishi"],
      bubbles=[("Hakobukun", "ワタシハ、呼バレテイマセン", ["ワタシハ、", "呼バレテイマセン"]), ("Engineer Shiraishi", "故障の報告はない。ただ、使われなくなってた", ["故障の報告はない。", "ただ、使われなく", "なってた"])],
      extra=[C.F("right", "Shiraishi"), C.CLOSED, ("EYES:", ["Hakobukun's eyes stay the same two vertical white ovals; only the wavy mouth shows the trouble"]),
             ("KATAKANA CHECK:", ["in ワタシハ、呼バレテイマセン every kana is KATAKANA: ワ, タ, シ, ハ, バ, レ, テ, イ, マ, セ, ン"])],
      scene="In a corner of the corridor, Hakobukun sits fully charged, waiting. No breakdown reports — it simply stopped being used."),
 dict(chars=["白石", "寺島", "ハコブ"], mouth="HAPPY", full=["白石", "寺島"], prop=("TABLET", [TAB + " that Shiraishi holds up toward Terashima; its screen shows just TWO plain bars side by side. NO letters, NO numbers"]),
      camera=["SHOT SIZE: full-body three-shot", "CAMERA POSITION: front-left, three-quarter", "CAMERA HEIGHT: 150 cm"],
      band="3% and 20%",
      comp=["Hakobukun is centred at about 20% of the image width, facing the two women, hopeful",
            "Shiraishi is centred at about 48% of the image width, body turned 40° toward Terashima, showing her the tablet",
            "Terashima is centred at about 76% of the image width, body turned 40° toward Shiraishi, arms folded, nodding with a small smile",
            C.SIZE("Shiraishi", C.LOWCHEST.format(n="Shiraishi")),
            "the bubble 「直ったなら、もう一回頼んでみる」 at the upper right, its tail down to Terashima"],
      bubbles=[("Nurse Terashima", "直ったなら、もう一回頼んでみる", ["直ったなら、", "もう一回頼んでみる"])],
      extra=[C.F("right", "Shiraishi and Terashima"), C.CLOSED, ("EFFECT:", ["two or three small sparkles beside Hakobukun's screen. NO letters"])],
      scene="Shiraishi puts the number of requests and the number of trips people made themselves side by side, fixes the cause of the two delays, and shows Terashima. Terashima agrees to try asking again."),
]

# 2026-11-14 バッチの下書き（1コマ2枚）と描き直しから、チェック①で選んだ
PICK = {1: '第149話_P1_20261114_004803b2.jpg', 2: '第149話_P2_20261114_004803b1.jpg', 3: '第149話_P3_20261114_004803b2.jpg', 4: '第149話_P4_20261114_004803b1.jpg'}

# 2026-11-14 チェック①（別のエージェント）：P3 のハコブくんが読者を向く・白石さんの胸で肩ひもが2本交差 → 向きを書き足し、鞄なしにする
PANELS[2]["extra"] = [C.TURN("right", "Shiraishi")] + PANELS[2]["extra"]
PANELS[2]["comp"].append("Shiraishi carries NO bag and NO shoulder strap in this picture")

# 2026-11-14 エイイチ「ハコブくん細すぎ。無理に入れ込まないで、人は多少脇にはみだしてもいい」→ P4 を描き直す
PANELS[3]["comp"] = ["Hakobukun is centred at about 19% of the image width, facing the two women, hopeful",
    "Shiraishi is centred at about 54% of the image width, body turned 40° toward Terashima, showing her the tablet",
    "Terashima is centred at about 86% of the image width, body turned 40° toward Shiraishi, arms folded, nodding with a small smile; her right shoulder and arm may be cut by the right edge",
    C.SIZE("Shiraishi", C.LOWCHEST.format(n="Shiraishi")),
    "the bubble 「直ったなら、もう一回頼んでみる」 at the upper right, its tail down to Terashima", "the text runs HORIZONTALLY"]
PANELS[3]["extra"] = [C.WIDE(2, 36)] + PANELS[3]["extra"]

# 2026-11-14 ハコブくんを太く描き直した版を選んだ
PICK[4] = "第149話_P4_20261114_012719.jpg"
```

### 第150話 spec.py

```python
EP, TITLE = 150, "7つのはずが28"
# 2026-11-14 カメラワーク：P1 白石さんの腰上・下から120cm、7つの箱を矢印でつないだホワイトボードを指す（自信）
# P2 白石さんの肩なめ140cm、奥でドロワーを拭く大城さんと、ドロワーを開けたハコブくん／P3 メモ帳の大写し（字にならない書き込みでびっしり）と、奥で青ざめる白石さん
# P4 斜め前150cm・3人の全身、長くなったメモ
import os
from importlib.machinery import SourceFileLoader as _L
C = _L("_c", os.path.expanduser("~/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/縦長/_common_148_152.py")).load_module()
MEMO = "a plain small spiral memo pad"
PANELS = [
 dict(chars=["白石"], no_yline=True, prop=("WHITEBOARD", ["a plain whiteboard on the wall behind her with exactly SEVEN empty rectangles drawn in one row, joined by six arrows. NO letters, NO numbers, NO words anywhere on the board"]),
      hscale_text=["Shiraishi is at the LEFT-CENTRE, waist-up, three-quarter view turned 40° to camera-right toward the board: x 10–56%, her head at about 30% of the image height",
                   "the whiteboard fills the right and upper part behind her: x 40–96%, y 30–64%"],
      camera=["SHOT SIZE: waist-up of Shiraishi with the board", "CAMERA POSITION: front-left, three-quarter", "CAMERA HEIGHT: 120 cm, looking slightly up at her"],
      band="3% and 22%",
      comp=["Shiraishi confident, pointing at the board with a marker pen held in one hand, her other hand on her hip",
            "the bubble 「検体を運ぶ手順は、7つです」 at the upper left, its tail down to Shiraishi"],
      bubbles=[("Engineer Shiraishi", "検体を運ぶ手順は、7つです", ["検体を運ぶ手順は、", "7つです"])],
      scene="A meeting room. Shiraishi draws the steps of carrying a specimen: call, load, send, arrive, notify, receive, return. Seven steps, she says."),
 dict(chars=["白石", "大城", "ハコブ"], mouth="NORMAL", no_yline=True, drawer=True, prop=("MEMO PAD AND CLOTH", [MEMO + " and a pen in Shiraishi's hands", "a plain white cleaning cloth in Oshiro's hand"]),
      hscale_text=["Shiraishi is in the near foreground at the LEFT, seen from her right rear three-quarter, chest-up, cut by the left and bottom edges: x 0–40%, her head at about 30% of the image height, writing on the memo pad",
                   "beyond her at the right-centre, about 2 m away, Oshiro and Hakobukun stand side by side on the same floor: Hakobukun at x 42–64%, three-quarter view facing Oshiro, its drawer pulled OPEN; Oshiro at x 62–88%, full body, leaning slightly to wipe the inside of the drawer with the cloth",
                   "Oshiro's head at about 32% and her shoes at about 90% of the image height; Hakobukun's top at about 52% and wheels at about 90% — the top of the robot reaches only the pit of Oshiro's stomach"],
      camera=["SHOT SIZE: over-the-shoulder — Shiraishi in the foreground, Oshiro and Hakobukun full body beyond", "CAMERA POSITION: behind Shiraishi's right shoulder", "CAMERA HEIGHT: 140 cm"],
      band="3% and 20%",
      thought=["Engineer Shiraishi"],
      comp=["Oshiro working briskly, used to it; a strip of tape stuck on the back of her hand",
            "the thought balloon 「拭いて、貼って、押し直して…」 at the upper left, its tail of small bubbles down to Shiraishi"],
      bubbles=[("Engineer Shiraishi", "拭いて、貼って、押し直して…", ["拭いて、貼って、", "押し直して…"])],
      extra=[C.F("right", "Oshiro")],
      scene="The next day Shiraishi spends a whole day on the ward next to Hakobukun. Oshiro wipes the drawer, tapes the slip on, presses the destination again... Shiraishi notes every step."),
 dict(chars=["白石"], no_yline=True, prop=("MEMO PAD", [MEMO + " held up LARGE in the foreground, both its pages crammed edge to edge with dense rows of tiny scribble marks and tick marks — NO legible letters, NO numbers"]),
      hscale_text=["the open memo pad fills the near foreground at the bottom, x 6–80%, y 52–100%, held in Shiraishi's two hands",
                   "Shiraishi's face is just beyond it at the upper centre, chest-up, pale and stunned: her head at about 18–40% of the image height, x 30–70%"],
      camera=["SHOT SIZE: close-up of the crammed memo pad with Shiraishi's stunned face beyond", "CAMERA POSITION: low in front of the memo pad", "CAMERA HEIGHT: 110 cm, looking up past the pad"],
      band="3% and 16%",
      comp=["vertical gloom lines behind Shiraishi; her glasses blank white with shock",
            "two bubbles: 「28個あった…」 at the upper left, tail down to Shiraishi; 「そう？いつもやってるから、手順だと思ってなかった」 at the upper right, smaller, its tail pointing out of the RIGHT edge of the picture toward Oshiro off-screen"],
      bubbles=[("Engineer Shiraishi", "28個あった…", ["28個あった…"]), ("Nurse Oshiro (off-screen, right)", "そう？いつもやってるから、手順だと思ってなかった", ["そう？いつもやってるから、", "手順だと", "思ってなかった"])],
      tails={"そう？いつもやってるから、手順だと思ってなかった": "out of the right edge, toward the off-screen nurse"},
      extra=[("EFFECT:", ["vertical gloom lines behind Shiraishi's head. NO letters"])],
      scene="Evening. Shiraishi's memo pad is crammed with steps: there were 28. Oshiro, off-screen, shrugs: she never thought of them as steps."),
 dict(chars=["白石", "大城", "ハコブ"], mouth="HAPPY", full=["白石", "大城"], prop=("LONG MEMO", ["a long paper strip of steps that Shiraishi holds; it hangs down to the floor and curls, covered in tiny scribble marks with some circled. NO legible letters, NO numbers"]),
      camera=["SHOT SIZE: full-body three-shot", "CAMERA POSITION: front-right, three-quarter", "CAMERA HEIGHT: 150 cm"],
      band="3% and 20%",
      comp=["Shiraishi is centred at about 24% of the image width, body turned 40° to camera-right, circling items on the long paper strip with a pen",
            "Hakobukun is centred at about 52% of the image width, looking at the paper strip, pleased",
            "Oshiro is centred at about 78% of the image width, body turned 40° to camera-left, arms folded, smiling: finally",
            C.SIZE("Oshiro", C.PIT.format(n="Oshiro")),
            "two bubbles: 「ワタシノ仕事ハ、28個デシタ」 at the upper left, tail down to Hakobukun; 「やっと数えてくれた」 at the upper right, tail down to Oshiro"],
      bubbles=[("Hakobukun", "ワタシノ仕事ハ、28個デシタ", ["ワタシノ仕事ハ、", "28個デシタ"]), ("Nurse Oshiro", "やっと数えてくれた", ["やっと", "数えてくれた"])],
      extra=[C.F("left", "the paper strip in Shiraishi's hands"), C.CLOSED, ("KATAKANA CHECK:", ["in ワタシノ仕事ハ、28個デシタ the ワ, タ, シ, ノ, ハ, デ, シ, タ are KATAKANA"])],
      scene="Shiraishi goes through the 28 steps one by one and circles the ones Hakobukun's side could remove. Hakobukun: my job was 28 steps. Oshiro: finally someone counted."),
]

# 2026-11-14 チェック①：P1 の2枚とも NG（1枚目は台本にない『』、2枚目は「運ぶを手順」と字が違う・縦書き）→ 字を確かめる行を足して描き直す
PANELS[0]["extra"] = [("TEXT CHECK:", ["the bubble reads exactly 検体を運ぶ手順は、 on the first line and 7つです on the second — 13 characters in total, in this order: 検 体 を 運 ぶ 手 順 は 、 7 つ で す",
                                     "there is NO を between 運ぶ and 手順", "NO 『』 or 「」 marks anywhere in the bubble", "the text runs HORIZONTALLY"])]

# 2026-11-14 バッチの下書き（1コマ2枚）と描き直しから、チェック①で選んだ
PICK = {1: '第150話_P1_20261114_004950.jpg', 2: '第150話_P2_20261114_004803b1.jpg', 3: '第150話_P3_20261114_004803b1.jpg', 4: '第150話_P4_20261114_004803b2.jpg'}

# 2026-11-14 チェック①（別のエージェント）：P3 で誰の手か分からない大きな手が手帳を持ち、手帳に英字もどきと番号 → 白石さんが自分の両手で胸の前に掲げる形にし、書き込みは波線だけ
PANELS[2] = dict(chars=["白石"], no_yline=True, prop=("MEMO PAD", [MEMO + ", open, both pages crammed edge to edge with dense rows of short WAVY LINES and small tick marks — the marks are abstract lines ONLY: NO letters of any alphabet, NO numbers, NO words"]),
      hscale_text=["Shiraishi is at the centre, chest-up, facing camera at a slight three-quarter angle: x 14–86%, her head at about 22–46% of the image height",
                   "she holds the open memo pad up in front of her chest with HER OWN TWO HANDS, both hands clearly connected to her own two arms: the pad at x 24–76%, y 50–88%"],
      camera=["SHOT SIZE: chest-up of Shiraishi holding up the crammed memo pad", "CAMERA POSITION: front-right, three-quarter", "CAMERA HEIGHT: 140 cm"],
      band="3% and 18%",
      comp=["Shiraishi pale and stunned, her glasses blank white with shock; vertical gloom lines behind her",
            "two bubbles: 「28個あった…」 at the upper left, tail down to Shiraishi; 「そう？いつもやってるから、手順だと思ってなかった」 at the upper right, smaller, its tail pointing out of the RIGHT edge of the picture toward Oshiro off-screen",
            "the text in both bubbles runs HORIZONTALLY"],
      bubbles=PANELS[2]["bubbles"], tails=PANELS[2]["tails"],
      extra=[("ANATOMY NOTE:", ["only Shiraishi is in the picture, with exactly TWO hands, both her own. NO extra hand entering from the edge"]), ("EFFECT:", ["vertical gloom lines behind Shiraishi's head. NO letters"])],
      scene=PANELS[2]["scene"])
# P4 のハコブくんが読者を向く → 向きを書き足す
PANELS[3]["extra"] = [C.TURN("right", "Oshiro")] + PANELS[3]["extra"]

# 2026-11-14 描き直しから選び直した
PICK[3] = "第150話_P3_20261114_005448.jpg"; PICK[4] = "第150話_P4_20261114_005453.jpg"

# 2026-11-14 エイイチ「ハコブくん細すぎ。無理に入れ込まないで、人は多少脇にはみだしてもいい」→ P4 を描き直す
PANELS[3]["comp"] = ["Shiraishi is centred at about 14% of the image width, body turned 40° to camera-right, circling items on the long paper strip with a pen; her left shoulder and arm may be cut by the left edge",
    "Hakobukun is centred at about 49% of the image width, looking at the paper strip, pleased",
    "Oshiro is centred at about 86% of the image width, body turned 40° to camera-left, arms folded, smiling: finally; her right shoulder may be cut by the right edge",
    C.SIZE("Oshiro", C.PIT.format(n="Oshiro")),
    "two bubbles: 「ワタシノ仕事ハ、28個デシタ」 at the upper left, tail down to Hakobukun; 「やっと数えてくれた」 at the upper right, tail down to Oshiro", "the text in both bubbles runs HORIZONTALLY"]
PANELS[3]["extra"] = [C.WIDE(32, 66)] + PANELS[3]["extra"]

# 2026-11-14 ハコブくんを太く描き直した版を選んだ
PICK[4] = "第150話_P4_20261114_012716.jpg"
```

### 第151話 spec.py

```python
EP, TITLE = 151, "着いたのに"
# 2026-11-14 カメラワーク：P1 斜め後ろ寄り・150cm、閉まった処置室の扉の前で止まるハコブくん（全身・ぽつん）
# P2 部屋の中、田中先生と大城さんの腰上・斜め前140cm（ハコブくんは出ない）／P3 真横・ハコブくん目線100cm、扉を開けて飛び出す大城さんとハコブくん（同じ床・全身）
# P4 斜め前・150cm、震える端末を手に早足で出てくる大城さんの膝上と、横で待つハコブくん
import os
from importlib.machinery import SourceFileLoader as _L
C = _L("_c", os.path.expanduser("~/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/縦長/_common_148_152.py")).load_module()
DOOR = "a plain closed sliding door of a treatment room standing on the floor, with a plain vertical handle and NO window, NO sign, NO text"
DEVICE = "a small plain hospital smartphone with a blank screen. NO text"
PANELS = [
 dict(chars=["ハコブ"], mouth="NORMAL", no_yline=True, prop=("DOOR", [DOOR + ", at the right of the picture, x 56–96%, from the floor at 90% up to 18% of the image height"]),
      hscale_text=["Hakobukun has stopped in front of the closed door at the left-centre, full body, three-quarter view facing the door (camera-right): its top at about 46% and wheels at about 88% of the image height, x 18–50%",
                   "the door is clearly much taller than the robot"],
      camera=["SHOT SIZE: full body of Hakobukun beside the tall closed door, lots of empty space", "CAMERA POSITION: front-left, three-quarter", "CAMERA HEIGHT: 150 cm"],
      band="3% and 18%",
      comp=["Hakobukun waiting politely, alone, a few drifting dots above its screen",
            "the bubble 「到着シマシタ」 at the upper left, SMALL, its tail down to Hakobukun"],
      bubbles=[("Hakobukun", "到着シマシタ", ["到着シマシタ"])],
      extra=[C.F("right", "the closed door"), C.CLOSED],
      scene="Night, the emergency department. Hakobukun brings urgent medicine and stops in front of the treatment room. It announces its arrival quietly. The door stays shut."),
 dict(chars=["田中", "大城"], no_yline=True,
      hscale_text=["Dr. Tanaka is at the LEFT-CENTRE, waist-up, three-quarter view turned 30° to camera-right, bent slightly forward over a treatment bed below the bottom edge (the patient is NOT shown): x 6–50%, his head at about 28% of the image height",
                   "Oshiro is at the RIGHT, waist-up, three-quarter view turned 30° to camera-left, holding an empty plain IV bag: x 54–92%, her head at about 30% of the image height"],
      camera=["SHOT SIZE: waist-up two-shot", "CAMERA POSITION: front, slightly right, across the treatment bed", "CAMERA HEIGHT: 140 cm"],
      band="3% and 20%",
      comp=["both busy and tense, sleeves rolled; Dr. Tanaka frowning, NOT looking at the door",
            "Oshiro glancing at the door, sweat drop",
            "two bubbles: 「薬は、まだか？」 at the upper left, tail down to Dr. Tanaka; 「まだです…」 at the upper right, smaller, tail down to Oshiro"],
      bubbles=[("Dr. Tanaka", "薬は、まだか？", ["薬は、まだか？"]), ("Nurse Oshiro", "まだです…", ["まだです…"])],
      extra=[("ANATOMY NOTE:", ["only Dr. Tanaka and Oshiro are in the picture. NO patient, NO third person, NO extra hands"])],
      scene="Inside the treatment room, Dr. Tanaka and Oshiro are in the middle of treatment. Nobody is watching the corridor. Dr. Tanaka asks if the medicine is here yet."),
 dict(chars=["大城", "ハコブ"], mouth="NORMAL", full=["大城"], prop=("DOOR", ["the same plain sliding door at the right edge, now flung OPEN, x 80–100%"]),
      camera=["SHOT SIZE: full-body two-shot, side view of the corridor", "CAMERA POSITION: from the side, slightly front", "CAMERA HEIGHT: 100 cm (Hakobukun's eye level)"],
      band="3% and 22%",
      comp=["Hakobukun is centred at about 30% of the image width, three-quarter view facing right toward Oshiro, calm",
            "Oshiro is centred at about 66% of the image width, having just burst out of the door, one foot forward, body turned toward the robot, eyes wide, shocked",
            C.SIZE("Oshiro", C.PIT.format(n="Oshiro")),
            "two bubbles: 「いつからいたの！」 at the upper right, tail down to Oshiro; 「少シ前カラ、到着シテイマシタ」 at the upper left, tail down to Hakobukun"],
      bubbles=[("Nurse Oshiro", "いつからいたの！", ["いつから", "いたの！"]), ("Hakobukun", "少シ前カラ、到着シテイマシタ", ["少シ前カラ、", "到着シテイマシタ"])],
      extra=[C.F("right", "Oshiro"), C.CLOSED, ("EFFECT:", ["a few short shock lines around Oshiro's head. NO letters"]), ("KATAKANA CHECK:", ["in 少シ前カラ、到着シテイマシタ the シ, カ, ラ, シ, テ, イ, マ, シ, タ are KATAKANA"])],
      scene="Oshiro steps out into the corridor and finds Hakobukun right there. When did you get here?! It says: a little while ago."),
 dict(chars=["大城", "ハコブ"], mouth="HAPPY", no_yline=True, prop=("DEVICE", [DEVICE + " in Oshiro's raised hand, buzzing"]),
      hscale_text=["Oshiro is at the RIGHT-CENTRE, knee-up, walking briskly toward camera-left out of the room, three-quarter view: x 44–88%, her head at about 18% of the image height, cut by the bottom edge at her knees",
                   "Hakobukun waits at the LEFT, about 1 m beyond her, three-quarter view facing her: x 6–40%, its top at about 52% of the image height, cut by the bottom edge below its drawer",
                   "Hakobukun's top edge is level with Oshiro's stomach — she is clearly taller"],
      camera=["SHOT SIZE: knee-up of Oshiro; Hakobukun beside her", "CAMERA POSITION: front-left, three-quarter", "CAMERA HEIGHT: 150 cm"],
      band="3% and 16%",
      comp=["Oshiro smiling, holding up the buzzing phone; she reached the robot right away",
            "two bubbles: 「届イタコトヲ、届ケマシタ」 at the upper left, tail down to Hakobukun; 「ちゃんと来たよ」 at the upper right, tail down to Oshiro"],
      bubbles=[("Hakobukun", "届イタコトヲ、届ケマシタ", ["届イタコトヲ、", "届ケマシタ"]), ("Nurse Oshiro", "ちゃんと来たよ", ["ちゃんと来たよ"])],
      extra=[C.F("right", "Oshiro"), C.CLOSED, ("EFFECT:", ["small vibration marks around the phone in her hand. NO letters"]), ("KATAKANA CHECK:", ["in 届イタコトヲ、届ケマシタ the イ, タ, コ, ト, ヲ, ケ, マ, シ, タ are KATAKANA"])],
      scene="Shiraishi has set it up so the arrival notice reaches the phone Oshiro carries and the pharmacy. The next night, the phone buzzes and Oshiro comes straight out."),
]

# 2026-11-14 チェック①：P4 が膝上ではなく全身で描かれ、P3 と同じ並び（左にハコブくん・右に全身の大城さん）になった → 大城さんを手前の胸上、ハコブくんを奥に
PANELS[3] = dict(chars=["大城", "ハコブ"], mouth="HAPPY", no_yline=True, prop=("DEVICE", [DEVICE + " in Oshiro's raised right hand, buzzing, LARGE in the foreground"]),
      hscale_text=["Oshiro is LARGE in the near foreground at the RIGHT, chest-up, three-quarter view turned 40° to camera-left, cut by the right and bottom edges: x 46–100%, her head at about 22–48% of the image height",
                   "her raised hand holds the buzzing phone at x 44–60%, y 40–58%",
                   "about 2 m beyond her at the LEFT, Hakobukun waits in the corridor, full body, three-quarter view facing her: its top at about 50% and wheels at about 82% of the image height, x 8–38%",
                   "Hakobukun is clearly SMALLER than Oshiro in the picture because it is farther away and much shorter"],
      camera=["SHOT SIZE: chest-up of Oshiro in the foreground; Hakobukun full body beyond", "CAMERA POSITION: front-left of Oshiro", "CAMERA HEIGHT: 150 cm"],
      band="3% and 18%",
      comp=["Oshiro smiling, glancing at the phone and then at the robot; she came straight out",
            "two bubbles: 「届イタコトヲ、届ケマシタ」 at the upper left, tail down to Hakobukun; 「ちゃんと来たよ」 at the upper right, tail down to Oshiro",
            "the text in both bubbles runs HORIZONTALLY"],
      bubbles=[("Hakobukun", "届イタコトヲ、届ケマシタ", ["届イタコトヲ、", "届ケマシタ"]), ("Nurse Oshiro", "ちゃんと来たよ", ["ちゃんと来たよ"])],
      extra=[C.F("right", "Oshiro"), C.CLOSED, ("EFFECT:", ["small vibration marks around the phone in her hand. NO letters"]), ("KATAKANA CHECK:", ["in 届イタコトヲ、届ケマシタ the イ, タ, コ, ト, ヲ, ケ, マ, シ, タ are KATAKANA"])],
      scene="The arrival notice now reaches the phone Oshiro carries and the pharmacy. The next night, the phone buzzes and Oshiro comes straight out to Hakobukun.")

# 2026-11-14 バッチの下書き（1コマ2枚）と描き直しから、チェック①で選んだ
PICK = {1: '第151話_P1_20261114_004803b2.jpg', 2: '第151話_P2_20261114_004803b1.jpg', 3: '第151話_P3_20261114_004803b1.jpg', 4: '第151話_P4_20261114_004948.jpg'}

# 2026-11-14 チェック①（別のエージェント）：P3 のハコブくんが大城さんでなく読者を向く → 向きを書き足す
PANELS[2]["extra"] = [C.TURN("right", "Oshiro")] + PANELS[2]["extra"]
PANELS[2]["comp"].append("the text in both bubbles runs HORIZONTALLY")

# 2026-11-14 描き直しから選び直した
PICK[3] = "第151話_P3_20261114_005450.jpg"
```

### 第152話 spec.py

```python
EP, TITLE = 152, "間に合うかどうか"
# 2026-11-14 カメラワーク：P1 斜め前120cm・全身、ドロワーに採血管の立てを入れる寺島さんとハコブくん（同じ床）
# P2 寺島さんの顔に寄る（受話器）・斜め右155cm／P3 低い60cm・引き、別の階のエレベーターの前で迷うハコブくん（ぽつん）
# P4 斜め前150cm・3人の全身、ストップウォッチの白石さん、ほっとしつつ首を振る寺島さん
import os
from importlib.machinery import SourceFileLoader as _L
C = _L("_c", os.path.expanduser("~/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/縦長/_common_148_152.py")).load_module()
RACK = "a small plain rack holding four plain blood sample tubes with plain caps. NO labels, NO text"
PANELS = [
 dict(chars=["寺島", "ハコブ"], mouth="NORMAL", full=["寺島"], drawer=True, prop=("SAMPLE RACK", [RACK + ", held in Terashima's two hands as she places it into Hakobukun's open drawer"]),
      camera=["SHOT SIZE: full-body two-shot", "CAMERA POSITION: front-right, three-quarter", "CAMERA HEIGHT: 120 cm"],
      band="3% and 20%",
      comp=["Hakobukun is centred at about 34% of the image width, its drawer pulled open toward Terashima",
            "Terashima is centred at about 66% of the image width, body turned 45° toward the robot, leaning slightly to put the rack into the drawer, cheerful",
            C.SIZE("Terashima", C.LOWCHEST.format(n="Terashima")),
            "the bubble 「検査室へ、お願いね」 at the upper right, its tail down to Terashima"],
      bubbles=[("Nurse Terashima", "検査室へ、お願いね", ["検査室へ、", "お願いね"])],
      extra=[C.F("right", "Terashima")],
      scene="Morning blood draws. Terashima puts the blood samples into Hakobukun's drawer: to the lab, please."),
 dict(chars=["寺島"], no_yline=True, prop=("HANDSET", ["a plain cordless internal phone handset at Terashima's ear. NO text"]),
      hscale_text=["Terashima's face and shoulders are LARGE, a close-up: x 14–88%, her face between 28% and 70% of the image height, the handset at her ear"],
      camera=["SHOT SIZE: close-up of Terashima's face", "CAMERA POSITION: front-right, three-quarter", "CAMERA HEIGHT: 155 cm"],
      band="3% and 24%",
      comp=["Terashima's eyes wide behind her round glasses, a cold sweat drop, shock lines",
            "two bubbles: 「午前の検体が、まだ届いてないんですけど」 at the upper left, its tail pointing to the handset (the lab's voice on the phone); 「えっ」 at the upper right, small, tail down to Terashima"],
      bubbles=[("Lab staff on the phone", "午前の検体が、まだ届いてないんですけど", ["午前の検体が、", "まだ届いて", "ないんですけど"]), ("Nurse Terashima", "えっ", ["えっ"])],
      tails={"午前の検体が、まだ届いてないんですけど": "the handset at Terashima's ear"},
      extra=[("EFFECT:", ["short shock lines around her head. NO letters"])],
      scene="Noon. The lab phones: the morning samples have not arrived. Terashima freezes."),
 dict(chars=["ハコブ"], mouth="TROUBLED", no_yline=True, prop=("ELEVATOR HALL", ["two plain closed elevator doors side by side on the wall behind, with plain call buttons. NO floor numbers, NO signs, NO text"]),
      hscale_text=["Hakobukun is SMALL at the centre of a wide empty space, full body, three-quarter view facing camera-left: its top at about 52% and wheels at about 80% of the image height, x 36–60%",
                   "the elevator doors are behind it, x 8–92%, y 20–80%, much taller than the robot"],
      camera=["SHOT SIZE: long shot — Hakobukun small and alone", "CAMERA POSITION: front-left, three-quarter", "CAMERA HEIGHT: 60 cm, low"],
      band="3% and 18%",
      comp=["Hakobukun lost, two small question marks drawn as plain symbols beside its screen",
            "the bubble 「現在地ヲ、確カメテイマス」 at the upper centre, its tail down to Hakobukun"],
      bubbles=[("Hakobukun", "現在地ヲ、確カメテイマス", ["現在地ヲ、", "確カメテイマス"])],
      extra=[C.CLOSED, ("EYES:", ["Hakobukun's eyes stay the same two vertical white ovals; only the wavy mouth shows the trouble"]), ("KATAKANA CHECK:", ["in 現在地ヲ、確カメテイマス the ヲ, カ, メ, テ, イ, マ, ス are KATAKANA"])],
      scene="Meanwhile, Hakobukun is stopped on a different floor, in front of the elevators, checking where it is."),
 dict(chars=["寺島", "白石", "ハコブ"], mouth="NORMAL", full=["寺島", "白石"], prop=("STOPWATCH", ["a plain stopwatch in Shiraishi's raised hand. NO numbers, NO text"]),
      camera=["SHOT SIZE: full-body three-shot", "CAMERA POSITION: front-left, three-quarter", "CAMERA HEIGHT: 150 cm"],
      band="3% and 20%",
      comp=["Terashima is centred at about 22% of the image width, body turned 40° to camera-right, relieved but shaking her head, one hand on her chest",
            "Hakobukun is centred at about 50% of the image width, earnest",
            "Shiraishi is centred at about 78% of the image width, body turned 40° to camera-left, holding up the stopwatch, determined",
            C.SIZE("Terashima", C.LOWCHEST.format(n="Terashima")),
            "two bubbles: 「間に合ったのは、運がよかっただけよね」 at the upper left, tail down to Terashima; 「間ニ合ウト、測レテカラ運ビマス」 at the upper right, tail down to Hakobukun"],
      bubbles=[("Nurse Terashima", "間に合ったのは、運がよかっただけよね", ["間に合ったのは、", "運がよかった", "だけよね"]), ("Hakobukun", "間ニ合ウト、測レテカラ運ビマス", ["間ニ合ウト、", "測レテカラ運ビマス"])],
      extra=[C.F("left", "Terashima"), C.CLOSED, ("KATAKANA CHECK:", ["in 間ニ合ウト、測レテカラ運ビマス the ニ, ウ, ト, レ, テ, カ, ラ, ビ, マ, ス are KATAKANA"])],
      scene="The samples were just barely usable. Terashima: we only made it by luck. Shiraishi will ask the lab how many minutes each sample can wait and time Hakobukun dozens of times. Until then, people carry the samples."),
]

# 2026-11-14 バッチの下書き（1コマ2枚）と描き直しから、チェック①で選んだ
PICK = {1: '第152話_P1_20261114_004803b1.jpg', 2: '第152話_P2_20261114_004803b2.jpg', 3: '第152話_P3_20261114_004803b1.jpg', 4: '第152話_P4_20261114_004803b1.jpg'}

# 2026-11-14 チェック①（別のエージェント）：P4 のハコブくんが読者を向く・ハコブくんの吹き出しのしっぽが白石さんとの間で止まる → 向きを書き足し、吹き出しをハコブくんの真上に
PANELS[3]["extra"] = [C.TURN("left", "Terashima")] + PANELS[3]["extra"]
PANELS[3]["comp"] = [c.replace("「間ニ合ウト、測レテカラ運ビマス」 at the upper right, tail down to Hakobukun", "「間ニ合ウト、測レテカラ運ビマス」 at the upper centre-right, its tail reaching all the way down to the TOP of Hakobukun's screen") for c in PANELS[3]["comp"]]

# 2026-11-14 描き直しから選び直した
PICK[4] = "第152話_P4_20261114_005450.jpg"

# 2026-11-14 エイイチ「ハコブくん細すぎ。無理に入れ込まないで、人は多少脇にはみだしてもいい」→ P1・P4 を描き直す
PANELS[0]["comp"] = ["Hakobukun is centred at about 32% of the image width, its drawer pulled open toward Terashima",
    "Terashima is centred at about 76% of the image width, body turned 45° toward the robot, leaning slightly to put the rack into the drawer, cheerful; her back may be cut by the right edge",
    C.SIZE("Terashima", C.LOWCHEST.format(n="Terashima")),
    "the bubble 「検査室へ、お願いね」 at the upper right, its tail down to Terashima", "the text runs HORIZONTALLY"]
PANELS[0]["extra"] = [C.WIDE(14, 50)] + PANELS[0]["extra"]
PANELS[3]["comp"] = ["Terashima is centred at about 13% of the image width, body turned 40° to camera-right, relieved but shaking her head, one hand on her chest; her left shoulder and arm may be cut by the left edge",
    "Hakobukun is centred at about 49% of the image width, earnest",
    "Shiraishi is centred at about 86% of the image width, body turned 40° to camera-left, holding up the stopwatch, determined; her right shoulder may be cut by the right edge",
    C.SIZE("Terashima", C.LOWCHEST.format(n="Terashima")),
    "two bubbles: 「間に合ったのは、運がよかっただけよね」 at the upper left, tail down to Terashima; 「間ニ合ウト、測レテカラ運ビマス」 at the upper centre-right, its tail reaching all the way down to the TOP of Hakobukun's screen", "the text in both bubbles runs HORIZONTALLY"]
PANELS[3]["extra"] = [C.WIDE(32, 66)] + PANELS[3]["extra"]

# 2026-11-14 ハコブくんを太く描き直した版を選んだ。P4 は左右の端に余計な人の肩が写ったので白で消す
PICK[1] = "第152話_P1_20261114_012718.jpg"; PICK[4] = "第152話_P4_20261114_012725.jpg"
PATCH = {4: [(0, 330, 30, 790), (1050, 330, 1080, 790)]}
```


## Gemini のバッチで、5話の下書き40枚が4分半で返った

5話の頼み文を作って、1コマ2枚ずつ、40枚をバッチで頼みました。バッチは Google の [Batch API](https://ai.google.dev/gemini-api/docs/batch-mode)（たくさんの頼みをまとめて送り、返りを待つ頼み方）で、料金はふつうの半分です。返りは最大24時間と案内されていますが、今回は4分半で全部返りました。

```
$ for e in 148 149 150 151 152; do python3 vbuild.py $e prompts; done
P1 8677 字 3 枚
P2 10161 字 6 枚
P3 11717 字 5 枚
P4 9617 字 4 枚
P1 5531 字 2 枚
P2 5641 字 2 枚
P3 11855 字 4 枚
P4 12289 字 5 枚
P1 5584 字 2 枚
P2 10760 字 6 枚
P3 5973 字 2 枚
P4 12739 字 5 枚
P1 8828 字 2 枚
P2 5843 字 4 枚
P3 11878 字 4 枚
P4 10103 字 4 枚
P1 11296 字 5 枚
P2 5627 字 2 枚
P3 8459 字 2 枚
P4 12457 字 5 枚

$ python3 vbatch.py submit 148 149 150 151 152 -n 2
第148話 プロンプトを作った
第149話 プロンプトを作った
第150話 プロンプトを作った
第151話 プロンプトを作った
第152話 プロンプトを作った
頼んだ: batches/vqk06wcirg83wslr842zvdkmynxuy4zqmefb 枚数 40 （第148・149・150・151・152話・1コマ2枚）

$ python3 vbatch.py status
20261114_004330 batches/vqk06wcirg83wslr842zvdkmynxuy4zqmefb 第148・149・150・151・152話 40 枚 BATCH_STATE_SUCCEEDED {"requestCount": "40", "successfulRequestCount": "40"}

$ python3 vbatch.py fetch
batches/vqk06wcirg83wslr842zvdkmynxuy4zqmefb 回収 40 枚・画像なし 0 ・入力トークン 135824 ・出力トークン 66493
```

![第148話のバッチの下書き。上の段と下の段が、同じ頼み文で描いた2枚](https://pixelstomotors.com/media/LOG508_02_batch_ep148.jpg)

下書きから1コマずつ選びました。選ぶときに見たのは、チェック項目（腕と脚の数・字・ハコブくんの形・大きさ・向き）と、隣のコマとの違いです。たとえば第148話の1コマ目は、上の段の絵に台本にない「」が付いていたので下の段を選び、4コマ目は下の段のハコブくんの体に「ハコブくん 115cm」と字が出ていたので上の段を選びました。


## 1コマずつの確認と、別のエージェントの確認で10コマを描き直した

### 自分の確認で描き直した2コマ

| コマ | 2枚ともだめだった理由 | 直し方 |
|---|---|---|
| 第150話 P1 | 1枚目は台本にない『』、2枚目は「検体を運ぶを手順は」と字が違い、縦書き | 1字ずつ並べた TEXT CHECK と「運ぶと手順の間に を はない」を足した |
| 第151話 P4 | 「膝上」の指定を無視して全身で描かれ、隣の P3 と同じ並び（左にハコブくん・右に全身の大城さん）になった | 大城さんを手前の胸上、ハコブくんを奥の全身にする重ねの構図に変えた |

第150話 P1 の描き直しは、2回のうち1回で絵が6枚返ってきました。その1回だけで約65円です（下の「費用」の節）。

### 別のエージェントに20コマを見てもらった

何話もまとめて描いたときは、描いた本人とは別のエージェントに1コマずつ見てもらう決まりです（チェック項目と過去の間違いの台帳）。20コマと5話の並びを渡すと、次の報告が返りました。

| 判定 | コマ | 理由 |
|---|---|---|
| 要修正 | 第150話 P3 | 左下から手帳を持つ大きな手が、誰の手か分からない。手帳に英語もどきの字と1〜20の番号 |
| 軽微 | 第148話 P3 | ハコブくんが寺島さんでなく読者を向く。カートが消え、「1時間後」が絵から分からない |
| 軽微 | 第149話 P3 | ハコブくんが白石さんでなく読者を向く。白石さんの胸で肩ひもが2本交差 |
| 軽微 | 第150話 P4・第151話 P3・第152話 P4 | ハコブくんが話し相手でなく読者を向く |
| 軽微 | 第152話 P4 | ハコブくんの吹き出しのしっぽが、ハコブくんと白石さんの間で止まる |
| 軽微 | 縦書きの吹き出し | 第148話 P3・第149話 P1・第151話 P2〜P4 など |
| OK | ほか | 字は全コマで台本と1字ずつ同じ。名札は白紙。背景の写り込みなし |

ハコブくんが読者を向く件は、LOG468 でエイイチに指摘されたことと同じでした。前回作った `face`（画面を相手のほうへ約60°）を入れていても起きたので、`TURN` を足しました。「輪のある側面が大きく手前を向き、画面は相手のほうへ急な角度で細く見える」と、見える面の形で書いています。6コマを1コマ2枚ずつ描き直し、第149話 P3 は2枚とも形と大きさが崩れたので前の絵のままにしました。

### 4コマ通しの確認で見たこと

1コマずつのあと、4コマを並べて話として読みました（LOG468 で足した手順）。第151話は、3コマ目と4コマ目が同じ並びになっていたので、4コマ目を描き直しています。第152話は、迷子のハコブくんを3コマ目に置き、「間に合ったのは、運がよかっただけ」と「測ってから運ぶ」を4コマ目にまとめました。


## エイイチの指摘で、細すぎたハコブくんを4コマ描き直した

確認用の絵を見せると、エイイチから次の指摘がありました。

> ハコブくん細すぎ。無理に入れ込まないで、人は多少脇にはみだしたりしてもいいよ

3人が並ぶコマで、人と人の間にハコブくんを押し込んだため、ハコブくんの幅が縮んで縦長になっていました。高さの比は合っていても、幅が足りません。ハコブくんの高さと幅の比は 1.7 : 1 と決まっています。

![左から、第149話 P4 の直す前と直した後、第152話 P4 の直す前と直した後。直す前はハコブくんが人の間で細く縮んでいる](https://pixelstomotors.com/media/LOG508_08_thin_hakobu.jpg)

- ねらい：ハコブくんの幅を、人より先に絵の中で取る
- 変わったこと：`WIDE` で「ハコブくんの体は画像の幅の x 32〜66%（約3分の1）」と書き、「人は左右の端で切れてよい・ロボットを細くするのはだめ」と書いた。人の位置は端へ寄せた
- 見込める効果：3人並びのコマで、ハコブくんが細くならない。次の話で、細さの指摘が出るかで確かめる

「端で切れてよい」と書くと、第152話 P4 では左右の端に知らない人の肩が写りました。仕上げのときに、その部分を白で塗っています（spec の PATCH）。チェック項目には「ハコブくんの幅が細くなっていない」を足しました。

![太く描き直した候補。左から第149話 P4・第150話 P4・第152話 P1・第152話 P4、上の段と下の段が同じ頼み文の2枚](https://pixelstomotors.com/media/LOG508_03_wide_redraw.jpg)

描き直したあとの高さの比は、`vbuild.py` の measure と目で測りました。

```
148 3 [(37, 41, 93, 52), (78, 18, 93, 75)] 比 0.69
149 3 [(30, 34, 93, 59), (74, 4, 95, 91)] 比 0.65
149 4 [(21, 40, 92, 52), (54, 18, 92, 74), (82, 18, 92, 73)] 比 0.70
150 2 [(23, 31, 100, 69), (70, 13, 75, 61)] 比 0.88
150 4 [(31, 20, 96, 77), (68, 20, 97, 77)] 比 1.00
151 3 [(28, 41, 92, 51), (76, 3, 93, 90)] 比 0.57
152 1 [(31, 12, 91, 79), (63, 34, 91, 57)] 比 0.72
152 4 [(18, 23, 96, 74), (52, 40, 95, 55), (82, 23, 97, 74)] 比 0.74
```

measure は、緑の背景の上の塊を床に立つものとして数えます。吹き出しや扉が人とつながると塊が大きくなり、比がずれます（149 P3・150 P2・150 P4・151 P3）。そのコマは目で測り、第148話 P3 は 0.76、第149話 P3 は 0.74、第150話 P4 は 0.65、第151話 P3 は 0.75 でした。並び図の比は、ハコブくん 115cm と寺島さん・白石さん 160cm で 0.72、大城さん 165cm で 0.70 です。


## ショート5本を作った（冒頭の動き・声・書き出し）

### 作業環境を前回の控えから戻した

ショートを作る道具は、第143〜147話のときに KIOXIA に残した控え（`_ショート専用/_取り込み_20261108/w143.tgz`）から戻しました。表紙と動きの字に使う Noto Sans CJK の Black の太さが作業環境になかったので、[noto-cjk](https://github.com/notofonts/noto-cjk) の NotoSansCJKjp-Black.otf を入れて、道具の中のフォントの場所を差し替えています。

### 冒頭の動き（motion148x.py）

ショートの冒頭の約3.6秒は、話ごとに違う動きをするハコブくんのアニメです。前の話の動きと重ねない決まりなので、5つとも新しく作りました。

| 話 | 冒頭の動き | 背景 |
|---|---|---|
| 148 | 来て止まり「積ミ込ミ／待チ」→ きょろきょろ → 画面の「…」が増えてゆっくり沈む | 夕方の廊下 |
| 149 | 隅でじっと待つ → ピコン！と跳ねて「呼バレタ？」→ しぼんで「…呼バレテ／ナイ」 | 物品の棚の前 |
| 150 | 画面の数字が 1〜7 と数え、止まらず 8〜28 と速くなる → 「28！」で×目、くらっと傾く | 休憩室 |
| 151 | 扉の前に止まって「到着／シマシタ」→ 前のめりに3回コツコツ → 「到着…」 | 夜の廊下 |
| 152 | 右へ左へ行ったり来たり（だんだん速く）→ 真ん中で「ココ／ドコ？」 | エレベーターの前 |

```python
"""第148〜152話の冒頭の動き（2026-11-14）。話ごとに違う動き（143〜147・138〜142 とも重ねない）
148 来て止まり「積ミ込ミ／待チ」→ 左右をきょろきょろ（誰も来ない）→ 時計の針のように画面に「…」が増え、ゆっくり沈む
149 隅でじっと待つ → ピコン！と跳ね上がって「呼バレタ？」→ 何も来ない → しぼんで「…呼バレテ／ナイ」
150 画面の数字が 1,2,3…7 と数える → 止まらず 8…28 と速くなり、体がふるえる → 「28！」で×目、くらっと傾く
151 扉の前（左）に止まって「到着／シマシタ」→ 前のめりに3回コツコツ → 返事がなく、画面が小さく「到着…」
152 右へ進んで止まり、くるっと左へ → 左へ進んで止まり、くるっと右へ（速くなる）→ 真ん中で「ココ／ドコ？」"""
import sys
import numpy as np
import motion30
import hakobu_motion as M
from hakobu_motion import T, ease, _approach

class Wait148:
    dur = 3.6; empty = True
    def motion(self, t):
        if t < 0.7: return _approach(t, 0.7, 220)
        if t < 2.2:                                      # きょろきょろ（左右に向きを変える）
            v = t - 0.7; side = 1 if int(v / 0.375) % 2 == 0 else -1
            return dict(flip=(side < 0), rot=side * 1.5)
        v = min(1, (t - 2.2) / 1.0)                      # ゆっくり沈む
        return dict(dy=14 * ease(v), sy=1 - 0.05 * ease(v), rot=3 * ease(v))
    def face(self, t):
        if t < 0.7: return ("open", "open"), None, "mid"
        if t < 2.2: return T("積ミ込ミ", "待チ") if t < 1.2 else (("open", "open"), None, "mid")
        return T("…" * (1 + min(2, int((t - 2.2) / 0.4))))
    def ring(self, t): return 0.6 if t < 2.2 else 0.3
    def blur(self, t): return t < 0.7
    def dim(self, t): return 1.0 if t < 2.2 else 1.0 - 0.15 * min(1, (t - 2.2) / 1.0)
    def cam(self, t): return (0, 0)

class Hope149:
    dur = 3.6; empty = True
    PING = 1.0
    def motion(self, t):
        if t < self.PING: return dict(dy=-2 * abs(np.sin(2 * np.pi * 0.7 * t)))
        v = t - self.PING
        if v < 0.6: return dict(dy=-50 * np.sin(np.pi * v / 0.6), sy=1 + 0.05 * np.sin(np.pi * v / 0.6))   # ピョン！
        if t < 2.4: return dict(rot=1.5 * np.sin(2 * np.pi * 1.5 * (t - 1.6)))                            # 待つ
        w = min(1, (t - 2.4) / 0.6)                                                                          # しぼむ
        return dict(sx=1 - 0.05 * ease(w), sy=1 - 0.1 * ease(w), dy=14 * ease(w))
    def face(self, t):
        if t < self.PING: return ("down", "down"), None, "closed"
        if t < 2.4: return T("呼バレタ？")
        return T("…呼バレテ", "ナイ")
    def ring(self, t): return 1.0 if self.PING <= t < 1.6 else 0.4
    def blur(self, t): return False
    def dim(self, t): return 1.0
    def cam(self, t): return (0, 0)

class Count150:
    dur = 3.6; empty = True
    def n(self, t):
        if t < 1.4: return 1 + int(t / 0.2)               # 1〜7 をゆっくり
        if t < 2.6: return min(28, 8 + int((t - 1.4) / 1.2 * 21))   # 8〜28 を速く
        return 28
    def motion(self, t):
        if t < 1.4: return dict(sy=1 - 0.02 * abs(np.sin(2 * np.pi * 2.5 * t)))
        if t < 2.6:
            r = (t - 1.4) / 1.2
            return dict(dx=r * 6 * np.sin(2 * np.pi * 14 * t), rot=r * 2 * np.sin(2 * np.pi * 11 * t))
        v = t - 2.6; k = min(1, v / 0.4)
        return dict(rot=-9 * ease(k), dx=-12 * ease(k))
    def face(self, t):
        if t < 2.6: return T(str(self.n(t)))
        if t < 2.9: return T("28！")
        return ("x", "x"), None, "wave"
    def ring(self, t): return 0.5 + 0.5 * np.sin(2 * np.pi * 6 * t) if 1.4 <= t < 2.6 else 0.6
    def blur(self, t): return False
    def dim(self, t): return 1.0
    def cam(self, t): return (int(5 * np.sin(2 * np.pi * 17 * t)), 0) if 2.0 <= t < 2.6 else (0, 0)

class Knock151:
    dur = 3.6; empty = True
    KN = (1.2, 1.55, 1.9)
    def motion(self, t):
        if t < 0.8: return _approach(t, 0.8, 240)
        for k in self.KN:                                 # 前のめりにコツッ
            if k <= t < k + 0.25:
                return dict(rot=-6 * np.sin(np.pi * (t - k) / 0.25), dx=-8 * np.sin(np.pi * (t - k) / 0.25))
        if t >= 2.4:
            w = min(1, (t - 2.4) / 0.5)
            return dict(sy=1 - 0.04 * ease(w), dy=8 * ease(w))
        return {}
    def face(self, t):
        if t < 0.8: return ("open", "open"), None, "mid"
        if t < 2.4: return T("到着", "シマシタ")
        return T("到着…")
    def ring(self, t): return 1.0 if any(k <= t < k + 0.25 for k in self.KN) else 0.5
    def blur(self, t): return t < 0.8
    def dim(self, t): return 1.0
    def cam(self, t): return (0, 0)

class Lost152:
    dur = 3.6; empty = True
    TURN = (0.0, 0.9, 1.6, 2.1, 2.45)                    # 向きを変える時刻（だんだん速く）
    STOP = 2.75
    def motion(self, t):
        if t >= self.STOP:
            v = t - self.STOP; k = np.exp(-v * 8) * np.cos(2 * np.pi * 4 * v)
            return dict(rot=3 * k)
        i = sum(1 for f in self.TURN if t >= f) - 1
        a = self.TURN[i]; b = self.TURN[i + 1] if i + 1 < len(self.TURN) else self.STOP
        u = ease((t - a) / (b - a)); side = -1 if i % 2 == 0 else 1      # 左へ・右へ
        x0 = 0 if i == 0 else (-120 if i % 2 == 1 else 120)
        x1 = -120 if side < 0 else 120
        if i == len(self.TURN) - 1: x1 = 0
        return dict(dx=x0 + (x1 - x0) * u, flip=(side > 0), dy=-3 * abs(np.sin(2 * np.pi * 4 * t)))
    def face(self, t):
        if t < self.STOP: return ("open", "open"), None, "o"
        return T("ココ", "ドコ？")
    def ring(self, t): return 0.6
    def blur(self, t): return t < self.STOP
    def dim(self, t): return 1.0
    def cam(self, t): return (0, 0)

M.PATTERNS.update(ep148=Wait148, ep149=Hope149, ep150=Count150, ep151=Knock151, ep152=Lost152)
if __name__ == "__main__":
    for arg in sys.argv[1:]:
        nm, _, kv = arg.partition(":")
        pat = M.PATTERNS[nm]()
        for item in filter(None, kv.split(",")):
            k, v = item.split("=", 1)
            if k in motion30.STR: setattr(pat, k, v)
            else: setattr(pat, k, float(v))
        M.render(pat, f"ハコブくん_{nm}.mp4")
```

```
書き出し: ハコブくん_ep149.mp4
  ！体が映像の窓からはみ出す（頭・足が切れる）: 7コマ 例 [(1.2, 171, 845), (1.23, 164, 840), (1.27, 159, 836)]（秒, 上端, 下端）
書き出し: ハコブくん_ep151.mp4
  ！体が映像の窓からはみ出す（頭・足が切れる）: 10コマ 例 [(0.0, 278, 907), (0.03, 275, 904), (0.07, 274, 901)]（秒, 上端, 下端）
書き出し: ハコブくん_ep152.mp4
書き出し: ハコブくん_ep148.mp4
  ！体が映像の窓からはみ出す（頭・足が切れる）: 8コマ 例 [(0.0, 278, 907), (0.03, 275, 904), (0.07, 274, 901)]（秒, 上端, 下端）
書き出し: ハコブくん_ep150.mp4
```

148話と151話の知らせは、右の端から走ってくる最初の数コマで、体が画面の端にかかっているものです（前の話の「右から来て止まる」と同じ）。149話はピョンと跳ねる高さが窓の上に出ていたので、70 から 50 に下げて書き出し直しました（1コマだけ残った知らせは窓の左右の端で、体は切れていません）。

![5話の冒頭の動きを0.5秒おきに並べたもの。上から第148〜152話](https://pixelstomotors.com/media/LOG508_04_op_motions.jpg)

### 声（声.sh）

声は [ElevenLabs](https://elevenlabs.io/)（文章を人の声で読み上げるサービス）で作りました。鍵は KIOXIA に置いたファイルを `tts_eleven.py` が読みます。寺島さんの声はロボのアバターさんと同じ声、第152話の検査室の電話の声は槙原さんの声を借りています（作れる声の数の上限のため）。数字は読み間違えないように、「302号室」を「さんまるに号室」、「28個」を「にじゅうはっこ」と書いて読ませました。

5話をいっせいに頼むと、ElevenLabs から「同時に頼めるのは3つまで」と断られたので、1話ずつ順に頼み直しました。

```
試し_01 で止まりました: 429 {"detail":{"type":"rate_limit_error","code":"concurrent_limit_exceeded","message":"Too many concurrent requests. Your current subscription is associated with a maximum of 3 concurrent requests (running in parallel). This is done such that a single user does not overwhelm our systems and affect other
  ！白石 で止まりました
```

#### 第148話の声.sh

```bash
#!/bin/bash
# 第148話_積む人がいない のショートの声（2026-11-14）。鍵は KIOXIA の elevenlabs_api_key.txt を tts_eleven.py が読む
cd "$(dirname "$0")"
B=../../_制作ベース
[ -f "voice_eleven/ハコブ/試し_03.mp3" ] || python3 $B/tts_eleven.py --voice gWJpc8whMBqnF7Y0kc9f --name ハコブ --try "積み込みを、お待ちしています｜誰も、積みませんでした｜積む人がいない時は、後で来ます" || echo "  ！ハコブ で止まりました"
[ -f "voice_eleven/寺島/試し_02.mp3" ] || python3 $B/tts_eleven.py --voice yWM6Fym2UxDGZqWRoGLB --name 寺島 --try "さんまるに号室、急変です！｜ごめん、今日はそれどころじゃなくて…" || echo "  ！寺島 で止まりました"
[ -f "voice_eleven/大城/試し_01.mp3" ] || python3 $B/tts_eleven.py --voice PrFsT9a2iZebpuiWVeug --name 大城 --try "すぐ行く！" || echo "  ！大城 で止まりました"
[ -f "voice_eleven/白石/試し_01.mp3" ] || python3 $B/tts_eleven.py --voice orQ2eWONBvtrTIvdS8RS --name 白石 --try "積む人も、予定に入れなきゃね。" || echo "  ！白石 で止まりました"
```

#### 第149話の声.sh

```bash
#!/bin/bash
# 第149話_呼ばれなくなった のショートの声（2026-11-14）。鍵は KIOXIA の elevenlabs_api_key.txt を tts_eleven.py が読む
cd "$(dirname "$0")"
B=../../_制作ベース
[ -f "voice_eleven/白石/試し_02.mp3" ] || python3 $B/tts_eleven.py --voice orQ2eWONBvtrTIvdS8RS --name 白石 --try "あれ、ここ数日、配送の依頼がほとんどない｜故障の報告はない。ただ、使われなくなってた。" || echo "  ！白石 で止まりました"
[ -f "voice_eleven/寺島/試し_02.mp3" ] || python3 $B/tts_eleven.py --voice yWM6Fym2UxDGZqWRoGLB --name 寺島 --try "ハコブくん？この前、二回遅れたから、自分で行ったほうが確実なの。｜直ったなら、もう一回、頼んでみる。" || echo "  ！寺島 で止まりました"
[ -f "voice_eleven/ハコブ/試し_01.mp3" ] || python3 $B/tts_eleven.py --voice gWJpc8whMBqnF7Y0kc9f --name ハコブ --try "私は、呼ばれていません" || echo "  ！ハコブ で止まりました"
```

#### 第150話の声.sh

```bash
#!/bin/bash
# 第150話_7つのはずが28 のショートの声（2026-11-14）。鍵は KIOXIA の elevenlabs_api_key.txt を tts_eleven.py が読む
cd "$(dirname "$0")"
B=../../_制作ベース
[ -f "voice_eleven/白石/試し_03.mp3" ] || python3 $B/tts_eleven.py --voice orQ2eWONBvtrTIvdS8RS --name 白石 --try "検体を運ぶ手順は、七つです。｜拭いて、貼って、押し直して…｜にじゅうはっこ、あった…" || echo "  ！白石 で止まりました"
[ -f "voice_eleven/大城/試し_02.mp3" ] || python3 $B/tts_eleven.py --voice PrFsT9a2iZebpuiWVeug --name 大城 --try "そう？いつもやってるから、手順だと思ってなかった。｜やっと、数えてくれた。" || echo "  ！大城 で止まりました"
[ -f "voice_eleven/ハコブ/試し_01.mp3" ] || python3 $B/tts_eleven.py --voice gWJpc8whMBqnF7Y0kc9f --name ハコブ --try "私の仕事は、にじゅうはっこ、でした" || echo "  ！ハコブ で止まりました"
```

#### 第151話の声.sh

```bash
#!/bin/bash
# 第151話_着いたのに のショートの声（2026-11-14）。鍵は KIOXIA の elevenlabs_api_key.txt を tts_eleven.py が読む
cd "$(dirname "$0")"
B=../../_制作ベース
[ -f "voice_eleven/ハコブ/試し_03.mp3" ] || python3 $B/tts_eleven.py --voice gWJpc8whMBqnF7Y0kc9f --name ハコブ --try "到着しました｜少し前から、到着していました｜届いたことを、届けました" || echo "  ！ハコブ で止まりました"
[ -f "voice_eleven/田中/試し_01.mp3" ] || python3 $B/tts_eleven.py --voice ydyMD3g4SbEeNpF0wahP --name 田中 --try "薬は、まだか？" || echo "  ！田中 で止まりました"
[ -f "voice_eleven/大城/試し_03.mp3" ] || python3 $B/tts_eleven.py --voice PrFsT9a2iZebpuiWVeug --name 大城 --try "まだです…｜いつからいたの！｜ちゃんと来たよ。" || echo "  ！大城 で止まりました"
```

#### 第152話の声.sh

```bash
#!/bin/bash
# 第152話_間に合うかどうか のショートの声（2026-11-14）。鍵は KIOXIA の elevenlabs_api_key.txt を tts_eleven.py が読む
cd "$(dirname "$0")"
B=../../_制作ベース
[ -f "voice_eleven/寺島/試し_03.mp3" ] || python3 $B/tts_eleven.py --voice yWM6Fym2UxDGZqWRoGLB --name 寺島 --try "検査室へ、お願いね。｜えっ｜間に合ったのは、運がよかっただけよね。" || echo "  ！寺島 で止まりました"
[ -f "voice_eleven/槙原/試し_01.mp3" ] || python3 $B/tts_eleven.py --voice Sryf5xKmMsQChU3HsLhZ --name 槙原 --try "午前の検体が、まだ届いてないんですけど" || echo "  ！槙原 で止まりました"
[ -f "voice_eleven/ハコブ/試し_02.mp3" ] || python3 $B/tts_eleven.py --voice gWJpc8whMBqnF7Y0kc9f --name ハコブ --try "現在地を、確かめています｜間に合うと、測れてから運びます" || echo "  ！ハコブ で止まりました"
```

### ショートの台本（spec_ep148〜152.py）

字幕は1行14字・2行までの決まりなので、長いセリフは「||」で字幕の画面を分けています（第149話の「ハコブくん？||この前2回遅れたから、…」など）。

#### spec_ep148.py

```python
import os, sys
sys.path.insert(0, os.environ.get("W24", os.path.expanduser("~/w")) + "/specs"); from common24 import *
import common24
common24.BGMAP[148] = "第48話.png"
HERE = os.path.dirname(os.path.abspath(__file__))
NAME, EP = "第148話_積む人がいない", 148
HEAD = ["待っていても", "誰も来ない？"]
COMIC = C + "ep148.jpg"
P1, P2, P3, P4 = [(0, 1350 * i, 1080, 1350 * (i + 1)) for i in range(4)]
DUR = 3.6
MOTION = mot("ep148", EP)
SCENES = [
  dict(kind="op", video=anim(EP, "ep148"), dur=DUR, lines=[], se=[dict(at=0.0, f="se/se_dash.mp3", db=-12), dict(at=0.7, f="se/se_pikon.mp3", db=-10)]),
  dict(kind="panel", crop=P1, fit=False, lines=[hk(1, "積ミ込ミヲ、|【オ待チシテイマス】")]),
  dict(kind="panel", crop=P2, fit=False, se=[dict(at=0.0, f="se/se_dash.mp3", db=-10)], lines=[ns("寺島", 1, "【302号室】、急変です！"), ns("大城", 1, "【すぐ行く】！")]),
  dict(kind="panel", crop=P3, fit=False, lines=[hk(2, "誰モ、|【積ミマセンデシタ】"), ns("寺島", 2, "ごめん、今日は|【それどころじゃなくて】…")]),
  dict(kind="panel", crop=P4, fit=False, lines=[hk(3, "積ム人ガイナイ時ハ、|【後デ来マス】"), ns("白石", 1, "【積む人も】、|予定に入れなきゃね")]),
  end(("ロボットの仕事に", 74, "ink", 0.0), ("積む人の手間も", 74, "ink", 0.25), ("積む人", 120, "orange", 0.9), ("も数に入れる", 120, "orange", 0.9), ("（手がふさがったら、待たずに後で来る）", 34, "gray", 1.8)),
]
```

#### spec_ep149.py

```python
import os, sys
sys.path.insert(0, os.environ.get("W24", os.path.expanduser("~/w")) + "/specs"); from common24 import *
import common24
common24.BGMAP[149] = "第43話.png"
HERE = os.path.dirname(os.path.abspath(__file__))
NAME, EP = "第149話_呼ばれなくなった", 149
HEAD = ["故障はないのに", "なぜ呼ばれない？"]
COMIC = C + "ep149.jpg"
P1, P2, P3, P4 = [(0, 1350 * i, 1080, 1350 * (i + 1)) for i in range(4)]
DUR = 3.6
MOTION = mot("ep149", EP)
SCENES = [
  dict(kind="op", video=anim(EP, "ep149"), dur=DUR, lines=[], se=[dict(at=0.0, f="se/se_yusa.wav", db=-16), dict(at=1.0, f="se/se_pikon.mp3", db=-8)]),
  dict(kind="panel", crop=P1, fit=False, lines=[ns("白石", 1, "あれ、ここ数日、|【配送の依頼】がほとんどない")]),
  dict(kind="panel", crop=P2, fit=False, lines=[ns("寺島", 1, "ハコブくん？||この前2回遅れたから、|【自分で行ったほうが確実】なの")]),
  dict(kind="panel", crop=P3, fit=False, lines=[hk(1, "ワタシハ、|【呼バレテイマセン】"), ns("白石", 2, "故障の報告はない。|ただ、【使われなくなってた】")]),
  dict(kind="panel", crop=P4, fit=False, lines=[ns("寺島", 2, "直ったなら、|【もう一回頼んでみる】")]),
  end(("頼れないと", 74, "ink", 0.0), ("人は黙って自分でやる", 74, "ink", 0.25), ("使われた数", 120, "orange", 0.9), ("も見る", 120, "orange", 0.9), ("（苦情や故障の数だけでは分からない）", 34, "gray", 1.8)),
]
```

#### spec_ep150.py

```python
import os, sys
sys.path.insert(0, os.environ.get("W24", os.path.expanduser("~/w")) + "/specs"); from common24 import *
import common24
common24.BGMAP[150] = "第7話.png"
HERE = os.path.dirname(os.path.abspath(__file__))
NAME, EP = "第150話_7つのはずが28", 150
HEAD = ["手順は7つ", "のはずが…"]
COMIC = C + "ep150.jpg"
P1, P2, P3, P4 = [(0, 1350 * i, 1080, 1350 * (i + 1)) for i in range(4)]
DUR = 3.6
MOTION = mot("ep150", EP)
SCENES = [
  dict(kind="op", video=anim(EP, "ep150"), dur=DUR, lines=[], se=[dict(at=1.4, f="se/se_spin.wav", db=-14), dict(at=2.6, f="se/se_don.mp3", db=-8)]),
  dict(kind="panel", crop=P1, fit=False, lines=[ns("白石", 1, "検体を運ぶ手順は、|【7つです】")]),
  dict(kind="panel", crop=P2, fit=False, lines=[ns("白石", 2, "拭いて、貼って、|【押し直して】…")]),
  dict(kind="panel", crop=P3, fit=False, se=[dict(at=0.0, f="se/se_don.mp3", db=-10)], lines=[ns("白石", 3, "【28個】あった…"), ns("大城", 1, "そう？||いつもやってるから、|【手順だと思ってなかった】")]),
  dict(kind="panel", crop=P4, fit=False, lines=[hk(1, "ワタシノ仕事ハ、|【28個デシタ】"), ns("大城", 2, "【やっと数えてくれた】")]),
  end(("効果を測る前に", 74, "ink", 0.0), ("手順を数える", 74, "ink", 0.25), ("現場を見て", 120, "orange", 0.9), ("数える", 120, "orange", 0.9), ("（手順書ではなく、一日横で見る）", 34, "gray", 1.8)),
]
```

#### spec_ep151.py

```python
import os, sys
sys.path.insert(0, os.environ.get("W24", os.path.expanduser("~/w")) + "/specs"); from common24 import *
import common24
common24.BGMAP[151] = "第21話.png"
HERE = os.path.dirname(os.path.abspath(__file__))
NAME, EP = "第151話_着いたのに", 151
HEAD = ["着いたのに", "なぜ届かない？"]
COMIC = C + "ep151.jpg"
P1, P2, P3, P4 = [(0, 1350 * i, 1080, 1350 * (i + 1)) for i in range(4)]
DUR = 3.6
MOTION = mot("ep151", EP)
SCENES = [
  dict(kind="op", video=anim(EP, "ep151"), dur=DUR, lines=[], se=[dict(at=0.0, f="se/se_dash.mp3", db=-12), dict(at=1.2, f="se/se_pechin.wav", db=-14), dict(at=1.55, f="se/se_pechin.wav", db=-14), dict(at=1.9, f="se/se_pechin.wav", db=-14)]),
  dict(kind="panel", crop=P1, fit=False, lines=[hk(1, "【到着シマシタ】")]),
  dict(kind="panel", crop=P2, fit=False, lines=[ns("田中", 1, "薬は、【まだか】？"), ns("大城", 1, "まだです…")]),
  dict(kind="panel", crop=P3, fit=False, se=[dict(at=0.0, f="se/se_don.mp3", db=-10)], lines=[ns("大城", 2, "【いつからいたの】！"), hk(2, "少シ前カラ、|【到着シテイマシタ】")]),
  dict(kind="panel", crop=P4, fit=False, se=[dict(at=0.0, f="se/se_buzz.wav", db=-14)], lines=[hk(3, "【届イタコトヲ】、|届ケマシタ"), ns("大城", 3, "【ちゃんと来たよ】")]),
  end(("着いただけでは", 74, "ink", 0.0), ("届いていない", 74, "ink", 0.25), ("受け取る人", 120, "orange", 0.9), ("に知らせる", 120, "orange", 0.9), ("（誰の、どの端末に知らせるかを現場で試す）", 34, "gray", 1.8)),
]
```

#### spec_ep152.py

```python
import os, sys
sys.path.insert(0, os.environ.get("W24", os.path.expanduser("~/w")) + "/specs"); from common24 import *
import common24
common24.BGMAP[152] = "第83話.png"
HERE = os.path.dirname(os.path.abspath(__file__))
NAME, EP = "第152話_間に合うかどうか", 152
HEAD = ["その検体", "間に合う？"]
COMIC = C + "ep152.jpg"
P1, P2, P3, P4 = [(0, 1350 * i, 1080, 1350 * (i + 1)) for i in range(4)]
DUR = 3.6
MOTION = mot("ep152", EP)
SCENES = [
  dict(kind="op", video=anim(EP, "ep152"), dur=DUR, lines=[], se=[dict(at=0.0, f="se/se_dash.mp3", db=-12), dict(at=2.75, f="se/se_pikon.mp3", db=-10)]),
  dict(kind="panel", crop=P1, fit=False, lines=[ns("寺島", 1, "【検査室へ】、お願いね")]),
  dict(kind="panel", crop=P2, fit=False, lines=[ns("槙原", 1, "午前の検体が、|【まだ届いてない】んですけど", phone=True), ns("寺島", 2, "【えっ】")]),
  dict(kind="panel", crop=P3, fit=False, lines=[hk(1, "現在地ヲ、|【確カメテイマス】")]),
  dict(kind="panel", crop=P4, fit=False, lines=[ns("寺島", 3, "間に合ったのは、|【運がよかっただけ】よね"), hk(2, "間ニ合ウト、|【測レテカラ運ビマス】")]),
  end(("遅れが痛い物は", 74, "ink", 0.0), ("任せる前に", 74, "ink", 0.25), ("間に合うか", 120, "orange", 0.9), ("を測る", 120, "orange", 0.9), ("（確かめ終わるまで、人が運ぶ）", 34, "gray", 1.8)),
]
```

### 書き出したショートの長さと、冒頭3秒の音量

```
できあがり: out_第148話_積む人がいない.mp4 26.2 秒
できあがり: out_第150話_7つのはずが28.mp4 28.1 秒
できあがり: out_第149話_呼ばれなくなった.mp4 28.5 秒
できあがり: out_第151話_着いたのに.mp4 21.4 秒
できあがり: out_第152話_間に合うかどうか.mp4 23.7 秒

$ for e in 148 149 150 151 152; do ... ffmpeg -t 3 -i ... -af volumedetect ...; done
148 mean_volume: -20.9 dB max_volume: -1.5 dB 
149 mean_volume: -20.9 dB max_volume: -1.5 dB 
150 mean_volume: -18.8 dB max_volume: -1.5 dB 
151 mean_volume: -21.7 dB max_volume: -1.5 dB 
152 mean_volume: -21.1 dB max_volume: -1.5 dB 
```

5本とも30秒以内で、冒頭の3秒に音が出ています。書き出した動画は1.4秒おきのコマを並べて、字幕のはみ出しと吹き出しの切れがないかを見ました。

![第150話のショートを1.4秒ごとに並べたコマ。冒頭の動き、4コマ、締めの言葉の順](https://pixelstomotors.com/media/LOG508_05_short_ep150_frames.jpg)

できあがった動画とタイトル・説明文は、KIOXIA の `videos/_ショート専用/_未アップ/第NNN話_…/` に置きました。YouTube の公開予約はまだです。


## 表紙・英語版を作り、サイトに10ページを公開した

### Gemini の月の上限で、表紙の編集が断られた

表紙は、見せ場のコマから吹き出しを消し、上に「第N話 題」を重ねて作ります。前回までは吹き出しを消すのに Gemini の編集を使っていましたが、今回は5回とも次のように断られました。

```
失敗 ep152_P3 429 {
  "error": {
    "code": 429,
    "message": "Your project has exceeded its monthly spending cap. Please go to AI Studio at https://ai.studio/spend to manage your project spend cap. Learn more at https://ai.google.dev/gemini-api/docs/billing#project-spend-caps. ",
    "status": "RESOURCE_EXHAUSTED
```

代わりに、吹き出しをプログラムで消しました。吹き出しは、黒い線で囲まれた白い領域です。上半分にある広い白い領域を見つけて、少しふくらませて白で塗り、まわりに残ったしっぽの先を消します。最初は、消し残しの小さな点を絵全体から消したので、リネンの袋の線や手帳の書き込み、「？」まで消えてしまいました。消すのは吹き出しのまわりだけにして、作り直しています。第148話は1コマ目だと袋の線まで消えたので、2コマ目（走る2人）を表紙にしました。

```python
"""第148〜152話の表紙用：選んだコマから吹き出しをプログラムで消す（2026-11-14）
Gemini の編集（cover_edit_143.py）が月の支払い上限で断られたため、代わりに作った。
白い閉じた領域のうち、上半分にあって広い（8,000画素以上・外枠の箱の55%以上が白）ものを吹き出しとみなし、
穴を埋めて9画素ふくらませた所を白で塗る。そのまわり45画素の中に残った小さな点（400画素未満の線の切れ端・しっぽの先）も消す。
第148話は P1 だとリネンの袋の線まで消えたので P2 にし、端に触れていて見つからなかった左の吹き出しは、上から290画素を白で塗って消した。"""
import numpy as np, os
from PIL import Image
from scipy import ndimage
B = os.path.expanduser("~/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/縦長")
PICK = {148: 2, 149: 3, 150: 3, 151: 1, 152: 3}
os.makedirs("cover_edit", exist_ok=True)
for e, k in PICK.items():
    im = Image.open(f"{B}/第{e}話/仕上げ/第{e}話_P{k}_1080x1350.png").convert("L"); a = np.asarray(im)
    white = a > 200; lab, n = ndimage.label(white)
    out = np.asarray(im.convert("RGB")).copy(); H, W = a.shape; er = np.zeros((H, W), bool)
    for i, sl in enumerate(ndimage.find_objects(lab), 1):
        ys, xs = sl; comp = lab[sl] == i; area = comp.sum(); h = ys.stop - ys.start; w = xs.stop - xs.start
        if ys.start == 0 or xs.start == 0 or ys.stop == H or xs.stop == W: continue
        if area < 8000 or ys.start > H * 0.45 or area / (h * w) < 0.55: continue
        m = np.zeros((H, W), bool); m[sl] = comp; m = ndimage.binary_dilation(ndimage.binary_fill_holes(m), iterations=9); er |= m
    out[er] = 255
    near = ndimage.binary_dilation(er, iterations=45)
    g = out.min(2) < 200; lab2, n2 = ndimage.label(g)
    for i, sl in enumerate(ndimage.find_objects(lab2), 1):
        c = lab2[sl] == i
        if c.sum() < 400 and (near[sl] & c).sum() == c.sum(): out[sl][c] = 255
    if e == 148:
        out[:290] = 255                 # 左の吹き出し（画像の端とつながっていて上の方法で見つからなかった）
        out[280:315, 455:505] = 255     # 残ったしっぽの先
    Image.fromarray(out).save(f"cover_edit/ep{e}_P{k}_nobubble.png")
```

```python
"""第148〜152話の表紙（2026-11-14）：上に「第N話 タイトル」、下に吹き出しを消したコマ（LOG468 の make_covers.py と同じ組み方）
Noto Sans CJK Black が作業環境になかったので、noto-cjk の NotoSansCJKjp-Black.otf を ~/fonts に入れて使った"""
import numpy as np, os
from PIL import Image, ImageDraw, ImageFont
FB = os.path.expanduser('~/fonts/NotoSansCJKjp-Black.otf')
B = os.path.expanduser("~/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/縦長")
T = {148: '積む人がいない', 149: '呼ばれなくなった', 150: '7つのはずが28', 151: '着いたのに', 152: '間に合うかどうか'}
PICK = {148: 2, 149: 3, 150: 3, 151: 1, 152: 3}
W, H = 1080, 1350; X0 = 56; MAXW = W - 2 * X0
font = ImageFont.truetype(FB, 96, index=0)
os.makedirs('media', exist_ok=True)
for e, k in PICK.items():
    p = Image.open(f'cover_edit/ep{e}_P{k}_nobubble.png').convert('RGB').resize((W, H), Image.LANCZOS)
    a = np.asarray(p).astype(np.int16); a[a.min(2) > 232] = 255; p = Image.fromarray(a.astype(np.uint8))
    head = f'第{e}話'; t = T[e]; one = f'{head} {t}'
    lines = [one] if font.getlength(one) <= MAXW else [head, t]
    c = Image.new('RGB', (W, H), 'white'); d = ImageDraw.Draw(c); y = 62
    for ln in lines:
        f = font
        while f.getlength(ln) > MAXW: f = ImageFont.truetype(FB, f.size - 4, index=0)
        d.text((X0, y), ln, font=f, fill=(36, 36, 36)); y += f.size + 18
    top = y + 40; A = H - top
    g = np.asarray(p.convert('L')) < 235; ys = np.where(g.any(1))[0]; xs = np.where(g.any(0))[0]
    cy0, cy1, cx0, cx1 = ys.min(), ys.max(), xs.min(), xs.max(); ch = cy1 - cy0
    s = min(A * 0.94 / ch, 1.45) if ch < A else A / ch
    q = p.resize((round(W * s), round(H * s)), Image.LANCZOS)
    mx = (cx0 + cx1) / 2 * s; my = (cy0 + cy1) / 2 * s
    if s >= 1:
        L = int(min(max(mx - W / 2, 0), q.width - W)); Tp = int(min(max(my - A / 2, 0), q.height - A))
        b = Image.new('RGB', (W, A), 'white'); b.paste(q.crop((L, Tp, min(q.width, L + W), min(q.height, Tp + A))), (0, 0)); c.paste(b, (0, top))
    else:
        t0 = int(cy0 * s); b = Image.new('RGB', (q.width, A), 'white'); b.paste(q.crop((0, t0, q.width, min(q.height, t0 + A))), (0, 0)); c.paste(b, ((W - q.width) // 2, top))
    c.save(f'media/manga_ep{e}_cover.webp', 'WEBP', quality=88, method=6)
    c.save(f'cover_edit/ep{e}_cover_ja.png')
    for i in range(1, 5):
        Image.open(f'{B}/第{e}話/仕上げ/第{e}話_P{i}_1080x1350.png').convert('RGB').save(f'media/manga_ep{e}_p{i}.webp', 'WEBP', quality=88, method=6)
```

![第148〜152話の表紙](https://pixelstomotors.com/media/LOG508_06_covers.jpg)

### 英語版（en_manga.py の指定）

英語版は描き直さず、吹き出しの中の日本語を消して英語を組みます（LOG416 の en_manga.py）。`seed` は各吹き出しの白い内側の1点です。白い閉じた領域を探すプログラムで座標を出し、どの吹き出しかを見て当てました。外枠が絵の端とつながって見つからなかった2つの吹き出し（第150話 P4・第152話 P4 のハコブくんの吹き出し）は、絵を見て座標を書いています。

#### 英語版の指定 ep148.json

```json
{"ep": 148, "cover_line": "NOBODY CAME TO LOAD ME.", "panels": [{"bubbles": [{"seed": [726, 249], "text": "Waiting\nto be loaded."}], "signs": []}, {"bubbles": [{"seed": [331, 90], "text": "Room 302,\nemergency!"}, {"seed": [807, 187], "text": "On my way!"}], "signs": []}, {"bubbles": [{"seed": [310, 255], "text": "Nobody\nloaded me."}, {"seed": [652, 358], "text": "Sorry, today\nwas just\ntoo much…"}], "signs": []}, {"bubbles": [{"seed": [344, 191], "text": "When no one can load,\nI'll come back later."}, {"seed": [890, 92], "text": "We have to schedule\nthe loaders too."}], "signs": []}]}
```

#### 英語版の指定 ep149.json

```json
{"ep": 149, "cover_line": "NO ONE CALLS ME ANYMORE.", "panels": [{"bubbles": [{"seed": [903, 405], "text": "Huh, almost\nno delivery\nrequests\nlately."}], "signs": []}, {"bubbles": [{"seed": [449, 312], "text": "Hakobukun? It was\nlate twice, so going\nmyself is more reliable."}], "signs": []}, {"bubbles": [{"seed": [353, 202], "text": "No one\nis calling me."}, {"seed": [958, 128], "text": "No fault reports.\nIt just stopped\nbeing used."}], "signs": []}, {"bubbles": [{"seed": [869, 103], "text": "If it's fixed,\nI'll try asking again."}], "signs": []}]}
```

#### 英語版の指定 ep150.json

```json
{"ep": 150, "cover_line": "SEVEN STEPS? IT WAS 28.", "panels": [{"bubbles": [{"seed": [375, 189], "text": "Carrying a specimen\ntakes 7 steps."}], "signs": []}, {"bubbles": [{"seed": [468, 210], "text": "Wipe, tape,\npress again…"}], "signs": []}, {"bubbles": [{"seed": [258, 46], "text": "There were\n28…"}, {"seed": [824, 47], "text": "Really? I do it every day,\nso I never thought\nof them as steps."}], "signs": []}, {"bubbles": [{"seed": [330, 110], "text": "My job was\n28 steps."}, {"seed": [838, 49], "text": "Finally,\nsomeone counted."}], "signs": []}]}
```

#### 英語版の指定 ep151.json

```json
{"ep": 151, "cover_line": "WHEN DID YOU GET HERE?!", "panels": [{"bubbles": [{"seed": [294, 132], "text": "I have\narrived."}], "signs": []}, {"bubbles": [{"seed": [210, 279], "text": "Is the\nmedicine\nhere yet?"}, {"seed": [927, 197], "text": "Not\nyet…"}], "signs": []}, {"bubbles": [{"seed": [464, 150], "text": "When did you\nget here?!"}, {"seed": [411, 328], "text": "A little while ago.\nI've been here."}], "signs": []}, {"bubbles": [{"seed": [365, 240], "text": "I delivered\nthe news\nthat I arrived."}, {"seed": [571, 256], "text": "I came\nright\naway."}], "signs": []}]}
```

#### 英語版の指定 ep152.json

```json
{"ep": 152, "cover_line": "WILL IT MAKE IT IN TIME?", "panels": [{"bubbles": [{"seed": [925, 322], "text": "To the lab,\nplease."}], "signs": []}, {"bubbles": [{"seed": [489, 186], "text": "The morning samples\nhaven't arrived yet…"}, {"seed": [885, 184], "text": "What?"}], "signs": []}, {"bubbles": [{"seed": [648, 179], "text": "Checking my\ncurrent location."}], "signs": []}, {"bubbles": [{"seed": [369, 143], "text": "We only made it\nby luck."}, {"seed": [760, 160], "text": "I'll carry them once\nI've measured that\nI'm on time."}], "signs": []}]}
```

![第148〜152話の英語版。各段の左4つが英語にしたコマ、右端が英語版の表紙](https://pixelstomotors.com/media/LOG508_07_english.jpg)

### R2 に画像を入れ、ページを公開した

```
$ python3 upload_media.py media media_en
入れる 50・入っている 0・名前の重なり 0・合計 8.3MB
  50/50
おわり

$ npx astro build
10:46:59 [build] 881 page(s) built in 31.05s
10:46:59 [build] Complete!

$ for e in 148 149 150 151 152; do echo "$e $(curl ... /manga/ep$e) $(curl ... /english-4-koma/ep$e)"; done
148 200 200
149 200 200
150 200 200
151 200 200
152 200 200
```

| 話 | 日本語のページの題 | 英語のページの題 |
|---|---|---|
| 148 | ロボットの仕事には、積む人の手間が含まれる | Robot Work Includes the People Who Load It |
| 149 | 頼れないと感じると、人は黙って自分でやり始める | When People Can't Rely on a Robot, They Quietly Do It Themselves |
| 150 | 手順書ではなく、現場を見て数える | Count the Steps on the Floor, Not in the Manual |
| 151 | 着いたことは、受け取る人に届いて初めて意味がある | Arriving Means Nothing Until Someone Knows |
| 152 | 遅れの代償が大きい物は、間に合うと確かめてから任せる | Before Trusting a Robot With Time-Critical Items, Measure It |

ページは pixelstomotors.com/manga/ep148 〜 ep152 と pixelstomotors.com/english-4-koma/ep148 〜 ep152 です。p2m-site への commit は `944f97b` です。

```text
commit 944f97b4ddf64700cfbdda9075fc6ffcf0e21dd2
Author: PixelstoMotors <studio@adaptdesign.jp>
Date:   Sat Nov 14 10:47:08 2026 +0900

    4コマ第148〜152話（日本語・英語）を追加
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01LevEXTjc44BADYdkHD3BY8

 src/data/english/ep148.json |  1 +
 src/data/english/ep149.json |  1 +
 src/data/english/ep150.json |  1 +
 src/data/english/ep151.json |  1 +
 src/data/english/ep152.json |  1 +
 src/data/englishCards.json  | 60 +++++++++++++++++++++++++++++++++++++++++++++
 src/data/manga/ep148.json   |  1 +
 src/data/manga/ep149.json   |  1 +
 src/data/manga/ep150.json   |  1 +
 src/data/manga/ep151.json   |  1 +
 src/data/manga/ep152.json   |  1 +
 src/data/mangaCards.json    | 60 +++++++++++++++++++++++++++++++++++++++++++++
 12 files changed, 130 insertions(+)
```

#### ページのデータ（日本語）ep148.json

```json
{"slug": "ep148", "title": "ロボットの仕事には、積む人の手間が含まれる", "label": "MANGA EP148", "character": "ハコブくん", "description": "夕方の病棟、リネン庫の前でドロワーを開けて待つハコブくん。急変の対応で誰も来ず、1時間たってもドロワーは空のまま。「誰モ、積ミマセンデシタ」。積む人の手間まで数に入れて予定を組む意味を描く4コマ漫画、第148話。", "published": "2026-11-14T01:50:00.000Z", "cover": "/media/manga_ep148_cover.webp", "comic": "", "cover_alt": "第148話 積む人がいないの表紙。急変で走る大城さんと寺島さんの奥で、ハコブくんがドロワーを開けて待っている", "comic_alt": "", "format": "vertical", "panels": ["/media/manga_ep148_p1.webp", "/media/manga_ep148_p2.webp", "/media/manga_ep148_p3.webp", "/media/manga_ep148_p4.webp"], "panel_alts": ["リネン庫の前で、ハコブくんがドロワーを開けて待っている。横に使い終わったリネンの袋を積んだカート", "急変で、寺島さんと大城さんが廊下を走っていく。奥でハコブくんは待ったまま", "1時間後もハコブくんは同じ所にいて、戻ってきた寺島さんが手を上げて謝る", "白石さんが積み込みの時間と担当の表を持ち、ハコブくんは次の仕事へ向かう"], "body": "<p>■ 腕のないロボットは、積まれるのを待つしかない<br>夕方の病棟。ハコブくんはリネン庫の前でドロワーを開け、積ミ込ミヲ、オ待チシテイマス。ところが廊下の奥で急変があり、大城さんと寺島さんは走っていった。1時間たっても、ドロワーは空のまま。誰モ、積ミマセンデシタ。</p><p></p><p>■ 誰もいなければ、仕事が進まない<br><a href=\"https://www.hinnovic.org/post/a-robot-as-a-co-worker\">米国の病院で搬送ロボットを調べた研究の紹介（hinnovic）</a>では、ロボットは人が積まないと仕事が始まらず、急な対応が多い病棟では、急な事態に動けない非効率な働き手と見られていた。ロボットの仕事には、積む人の手間が入っている。</p><p></p><p>■ 結論：積む人も、予定の数に入れる<br>白石さんは積み込みの時間と担当を表にした。担当の手がふさがったら、ハコブくんは待ち続けず、先に別の仕事へ回る。積ム人ガイナイ時ハ、後デ来マス。<br>ロボットの予定を組むときは、そばで手を貸す人の予定も一緒に組む。</p>"}
```

#### ページのデータ（日本語）ep149.json

```json
{"slug": "ep149", "title": "頼れないと感じると、人は黙って自分でやり始める", "label": "MANGA EP149", "character": "ハコブくん", "description": "ここ数日、ハコブくんへの配送の依頼がほとんどない。寺島さんは「この前2回遅れたから、自分で行ったほうが確実なの」。故障の報告はないのに、使われなくなっていた。使われている数を見る意味を描く4コマ漫画、第149話。", "published": "2026-11-14T01:52:00.000Z", "cover": "/media/manga_ep149_cover.webp", "comic": "", "cover_alt": "第149話 呼ばれなくなったの表紙。隅の充電台で困り顔のハコブくんと、考え込む白石さん", "comic_alt": "", "format": "vertical", "panels": ["/media/manga_ep149_p1.webp", "/media/manga_ep149_p2.webp", "/media/manga_ep149_p3.webp", "/media/manga_ep149_p4.webp"], "panel_alts": ["白石さんがタブレットで記録を見る。依頼の数の棒グラフが右へ下がっていく", "寺島さんが薬の袋を持って、自分で薬剤部へ歩いていく", "隅の充電台で待つハコブくんと、あごに手を当てて考える白石さん", "白石さんがタブレットを見せ、寺島さんが腕を組んでうなずく。横でハコブくんがうれしそう"], "body": "<p>■ 故障はない。ただ、呼ばれない<br>白石さんが記録を見ると、ここ数日、ハコブくんへの配送の依頼がほとんどない。病棟へ行くと、寺島さんが自分で薬剤部へ向かうところだった。この前2回遅れたから、自分で行ったほうが確実なの。ハコブくんは隅の充電台で待っていた。ワタシハ、呼バレテイマセン。</p><p></p><p>■ 頼れないと、人は元のやり方に戻る<br><a href=\"https://vbn.aau.dk/ws/files/461098840/SHTI_286_SHTI210639.pdf\">デンマークの病院の搬送ロボットの研究（Tornbjerg ほか）</a>では、厨房スタッフ16人のうち8人が、ロボットを当てにできないと感じ、ロボットに任せるはずの仕事を自分でやっていた。苦情も故障の報告も出ないまま、使われなくなることがある。</p><p></p><p>■ 結論：使われている数も見続ける<br>白石さんは、頼まれた数と、人が自分で運んだ数を毎日の画面に並べた。遅れた2回の原因を直して見せると、寺島さんは言った。直ったなら、もう一回頼んでみる。<br>壊れていないことと、頼られていることは別に数える。</p>"}
```

#### ページのデータ（日本語）ep150.json

```json
{"slug": "ep150", "title": "手順書ではなく、現場を見て数える", "label": "MANGA EP150", "character": "ハコブくん", "description": "検体を運ぶ手順は7つです、と白石さん。一日ハコブくんの横で見ると、拭いて、貼って、押し直して…。メモ帳は28個の手順でびっしりになった。導入の効果を測る前に現場を見て数える意味を描く4コマ漫画、第150話。", "published": "2026-11-14T01:54:00.000Z", "cover": "/media/manga_ep150_cover.webp", "comic": "", "cover_alt": "第150話 7つのはずが28の表紙。書き込みでびっしりのメモ帳を持って、青ざめる白石さん", "comic_alt": "", "format": "vertical", "panels": ["/media/manga_ep150_p1.webp", "/media/manga_ep150_p2.webp", "/media/manga_ep150_p3.webp", "/media/manga_ep150_p4.webp"], "panel_alts": ["白石さんがホワイトボードの手順の図を指して、7つですと言う", "白石さんがメモを取り、奥で大城さんがハコブくんのドロワーを拭いている", "書き込みでびっしりのメモ帳を持って、白石さんが青ざめる", "長くなったメモを持つ白石さん、ハコブくん、腕を組んで笑う大城さん"], "body": "<p>■ 7つのはずだった<br>会議室で、白石さんは検体を運ぶ手順を書いた。呼ぶ・積む・送る・届く・知らせる・受け取る・戻る。7つです。翌日、一日ハコブくんの横で見ていると、大城さんはドロワーを拭き、伝票をテープで留め、行き先を押し直していた。夕方、メモ帳は28個の手順で埋まった。</p><p></p><p>■ 人が黙って支えている手順がある<br><a href=\"https://interactions.acm.org/archive/view/march-april-2025/the-invisible-work-of-human-robot-collaboration-when-streamlined-processes-meet-the-complexity-of-real-world-practices\">病院の搬送ロボットの運用を観察した報告（ACM Interactions）</a>では、管理側が説明した手順は7段階だったが、実際の運用を見ると28段階あった。差の分は、周りの人がロボットを支える見えない手間だった。</p><p></p><p>■ 結論：効果を測る前に、現場を見て数える<br>白石さんは増えた手順を一つずつ見て、ハコブくんの側で減らせるものに印を付けた。ワタシノ仕事ハ、28個デシタ。大城さんは言った。やっと数えてくれた。<br>手順書の数ではなく、現場で数えた数から始める。</p>"}
```

#### ページのデータ（日本語）ep151.json

```json
{"slug": "ep151", "title": "着いたことは、受け取る人に届いて初めて意味がある", "label": "MANGA EP151", "character": "ハコブくん", "description": "夜の救急外来。急ぎの薬を運んだハコブくんが処置室の前で「到着シマシタ」。でも中の大城さんと田中先生は処置の最中で、誰も廊下を見ていない。到着の知らせ方を現場で決める意味を描く4コマ漫画、第151話。", "published": "2026-11-14T01:56:00.000Z", "cover": "/media/manga_ep151_cover.webp", "comic": "", "cover_alt": "第151話 着いたのにの表紙。閉まった処置室の扉の前で、ハコブくんが待っている", "comic_alt": "", "format": "vertical", "panels": ["/media/manga_ep151_p1.webp", "/media/manga_ep151_p2.webp", "/media/manga_ep151_p3.webp", "/media/manga_ep151_p4.webp"], "panel_alts": ["夜の救急外来。閉まった処置室の扉の前で、ハコブくんが到着を知らせる", "処置室の中で、田中先生が薬はまだかと聞き、大城さんが扉のほうを見る", "扉から出てきた大城さんが、すぐ前にいたハコブくんに驚く", "次の夜、震えるスマホを手に大城さんが出てきて、ハコブくんが待っている"], "body": "<p>■ 着いていたのに、誰も知らなかった<br>夜の救急外来。ハコブくんは急ぎの薬を運び、処置室の前で止まった。到着シマシタ。中では大城さんと田中先生が処置の最中で、廊下を見ている人はいない。薬は、まだか？ 大城さんが廊下に出ると、ハコブくんはそこにいた。いつからいたの！</p><p></p><p>■ 知らせ方は、現場で試して作る<br><a href=\"https://www.cgh.com.sg/about-cgh/overview/cgh90thanniversary/care-grows-here/engineering-care-together\">シンガポールのチャンギ総合病院の記事</a>では、救急外来で薬を運ぶロボットの到着が看護師と薬剤師にすぐ伝わるよう、試作と試験を繰り返して知らせ方を作り直している。</p><p></p><p>■ 結論：誰の、どの端末に知らせるかを決める<br>白石さんは、到着の知らせが大城さんの持つ端末と薬剤部の両方に届くようにした。次の夜、端末が震えて大城さんがすぐ出てきた。届イタコトヲ、届ケマシタ。<br>運び終えたかどうかは、受け取る人に伝わったところで数える。</p>"}
```

#### ページのデータ（日本語）ep152.json

```json
{"slug": "ep152", "title": "遅れの代償が大きい物は、間に合うと確かめてから任せる", "label": "MANGA EP152", "character": "ハコブくん", "description": "採血の検体をハコブくんに託した寺島さん。昼になって検査室から「まだ届いてない」と電話。ハコブくんは別の階のエレベーターの前で迷っていた。間に合うと確かめてから任せる意味を描く4コマ漫画、第152話。", "published": "2026-11-14T01:58:00.000Z", "cover": "/media/manga_ep152_cover.webp", "comic": "", "cover_alt": "第152話 間に合うかどうかの表紙。エレベーターの前で、困った顔のハコブくんが迷っている", "comic_alt": "", "format": "vertical", "panels": ["/media/manga_ep152_p1.webp", "/media/manga_ep152_p2.webp", "/media/manga_ep152_p3.webp", "/media/manga_ep152_p4.webp"], "panel_alts": ["寺島さんが採血管の立てを、ハコブくんのドロワーに入れる", "検査室からの電話で、寺島さんが青ざめる", "別の階のエレベーターの前で、ハコブくんが迷っている", "胸に手を当てる寺島さん、ハコブくん、ストップウォッチを持つ白石さん"], "body": "<p>■ 検体が届いていない<br>朝の採血。寺島さんは採った血液をハコブくんに載せた。検査室へ、お願いね。昼、検査室から電話が来る。午前の検体が、まだ届いてないんですけど。ハコブくんは別の階で、現在地ヲ、確カメテイマス。検体はぎりぎり使えたが、寺島さんは首を振った。間に合ったのは、運がよかっただけよね。</p><p></p><p>■ 一度遅れると、もう任せてもらえない<br><a href=\"https://www.proofnews.org/moxi/\">米国の病院の看護ロボットの報道（Proof News）</a>では、ロボットが階をまたぐと迷うため、看護師が採血の検体を任せなくなったと伝えている。遅れた検体は捨てて採り直しになるからだ。</p><p></p><p>■ 結論：決まりの時間内に届くと測ってから任せる<br>白石さんは、検体ごとに何分以内に着けばよいかを検査室に聞き、ハコブくんで何十回も走って測ることにした。確かめ終わるまで、検体は人が運ぶ。間ニ合ウト、測レテカラ運ビマス。</p>"}
```

#### ページのデータ（英語）ep148.json

```json
{"slug": "ep148", "title": "Robot Work Includes the People Who Load It", "label": "MANGA EP148EN", "character": "ハコブくん", "description": "Hakobukun waits by the linen room with its drawer open. An emergency pulls the nurses away, and an hour later the drawer is still empty. A manga on counting the people who load a robot as part of its work.", "published": "2026-11-14T01:51:00.000Z", "cover": "/media/manga_ep148en_cover.webp", "comic": "", "cover_alt": "Episode 148 cover: Nobody came to load me. Oshiro and Terashima run past while Hakobukun waits with its drawer open", "comic_alt": "", "format": "vertical", "panels": ["/media/manga_ep148en_p1.webp", "/media/manga_ep148en_p2.webp", "/media/manga_ep148en_p3.webp", "/media/manga_ep148en_p4.webp"], "panel_alts": ["Hakobukun waits by the linen room with its drawer open, next to a cart of used linen bags", "Terashima and Oshiro run down the corridor to an emergency; Hakobukun keeps waiting behind them", "An hour later Hakobukun is still in the same place; Terashima comes back and apologizes", "Shiraishi holds a loading schedule as Hakobukun heads off to its next job"], "body": "<p>■ A robot without arms can only wait to be loaded<br>Evening on the ward. Hakobukun opens its drawer in front of the linen room: \"Waiting to be loaded.\" Then a patient takes a turn for the worse, and Oshiro and Terashima run off. An hour later, the drawer is still empty. \"Nobody loaded me.\"</p><p></p><p>■ If nobody is there, the work does not get done<br>In <a href=\"https://www.hinnovic.org/post/a-robot-as-a-co-worker\">an article on a study of delivery robots in a U.S. hospital (hinnovic)</a>, the robots could not start until someone loaded them, and on busy wards they were seen as inefficient workers that could not react to urgent situations. A robot's work includes the work of the people who load it.</p><p></p><p>■ Conclusion: Count the loaders in the schedule<br>Shiraishi made a timetable of loading times and who loads. If that person's hands are full, Hakobukun does not wait forever; it goes to another job first. \"When no one can load, I'll come back later.\"<br>When you schedule the robot, schedule the people beside it too.</p>"}
```

#### ページのデータ（英語）ep149.json

```json
{"slug": "ep149", "title": "When People Can't Rely on a Robot, They Quietly Do It Themselves", "label": "MANGA EP149EN", "character": "ハコブくん", "description": "Delivery requests for Hakobukun have almost stopped. Terashima walks to the pharmacy herself: it was late twice. No fault reports, yet it had stopped being used. A manga on watching how often a robot is actually used.", "published": "2026-11-14T01:53:00.000Z", "cover": "/media/manga_ep149en_cover.webp", "comic": "", "cover_alt": "Episode 149 cover: No one calls me anymore. Hakobukun on its charging dock in a corner, Shiraishi thinking", "comic_alt": "", "format": "vertical", "panels": ["/media/manga_ep149en_p1.webp", "/media/manga_ep149en_p2.webp", "/media/manga_ep149en_p3.webp", "/media/manga_ep149en_p4.webp"], "panel_alts": ["Shiraishi checks a tablet; the bar chart of requests falls to almost nothing", "Terashima walks to the pharmacy herself, carrying a paper bag", "Hakobukun waits on its charging dock in a corner while Shiraishi thinks", "Shiraishi shows the tablet, Terashima nods with arms folded, and Hakobukun looks pleased"], "body": "<p>■ Nothing is broken. It just isn't called<br>Checking the records, Shiraishi finds almost no delivery requests for Hakobukun in the past few days. On the ward, Terashima is heading to the pharmacy herself: \"It was late twice, so going myself is more reliable.\" Hakobukun sits on its charging dock. \"No one is calling me.\"</p><p></p><p>■ When people can't rely on it, they go back to the old way<br>In <a href=\"https://vbn.aau.dk/ws/files/461098840/SHTI_286_SHTI210639.pdf\">a study of delivery robots at a Danish hospital (Tornbjerg et al.)</a>, 8 of 16 kitchen staff felt they could not rely on the robots and did the robots' tasks themselves. A robot can fall out of use without a single complaint or fault report.</p><p></p><p>■ Conclusion: Keep watching how often it is used<br>Shiraishi put the number of requests next to the number of trips people made themselves, fixed the cause of the two delays, and showed Terashima. \"If it's fixed, I'll try asking again.\"<br>Count \"not broken\" and \"relied on\" separately.</p>"}
```

#### ページのデータ（英語）ep150.json

```json
{"slug": "ep150", "title": "Count the Steps on the Floor, Not in the Manual", "label": "MANGA EP150EN", "character": "ハコブくん", "description": "Shiraishi says carrying a specimen takes seven steps. After a day beside Hakobukun, her memo pad is full: wipe, tape, press again… There were 28. A manga on counting real steps before measuring a robot's effect.", "published": "2026-11-14T01:55:00.000Z", "cover": "/media/manga_ep150en_cover.webp", "comic": "", "cover_alt": "Episode 150 cover: Seven steps? It was 28. Shiraishi turns pale, holding a memo pad crammed with notes", "comic_alt": "", "format": "vertical", "panels": ["/media/manga_ep150en_p1.webp", "/media/manga_ep150en_p2.webp", "/media/manga_ep150en_p3.webp", "/media/manga_ep150en_p4.webp"], "panel_alts": ["Shiraishi points at the steps on the whiteboard: seven", "Shiraishi takes notes while Oshiro wipes Hakobukun's drawer", "Shiraishi turns pale, holding a memo pad crammed with notes", "Shiraishi with a long strip of notes, Hakobukun, and Oshiro smiling with arms folded"], "body": "<p>■ It was supposed to be seven<br>In the meeting room, Shiraishi wrote out the steps for carrying a specimen: call, load, send, arrive, notify, receive, return. \"Seven steps.\" The next day she spent the whole day beside Hakobukun. Oshiro wiped the drawer, taped on the slip, pressed the destination again… By evening, her memo pad held 28 steps.</p><p></p><p>■ Some steps are quietly carried by people<br>In <a href=\"https://interactions.acm.org/archive/view/march-april-2025/the-invisible-work-of-human-robot-collaboration-when-streamlined-processes-meet-the-complexity-of-real-world-practices\">a report observing hospital delivery robots (ACM Interactions)</a>, management described a seven-step process, but observation showed 28 steps in actual operation. The gap was the invisible work of the people around the robot.</p><p></p><p>■ Conclusion: Count on the floor before measuring the effect<br>Shiraishi went through the extra steps one by one and marked the ones Hakobukun's side could remove. \"My job was 28 steps.\" Oshiro: \"Finally, someone counted.\"<br>Start from the number you counted on the floor, not the one in the manual.</p>"}
```

#### ページのデータ（英語）ep151.json

```json
{"slug": "ep151", "title": "Arriving Means Nothing Until Someone Knows", "label": "MANGA EP151EN", "character": "ハコブくん", "description": "Night in the emergency department. Hakobukun brings urgent medicine and announces its arrival outside the treatment room, but everyone inside is busy. A manga on deciding how a robot tells people it has arrived.", "published": "2026-11-14T01:57:00.000Z", "cover": "/media/manga_ep151en_cover.webp", "comic": "", "cover_alt": "Episode 151 cover: When did you get here?! Hakobukun waits in front of the closed treatment room door", "comic_alt": "", "format": "vertical", "panels": ["/media/manga_ep151en_p1.webp", "/media/manga_ep151en_p2.webp", "/media/manga_ep151en_p3.webp", "/media/manga_ep151en_p4.webp"], "panel_alts": ["Night in the emergency department; Hakobukun announces its arrival in front of the closed door", "Inside, Dr. Tanaka asks if the medicine is here yet; Oshiro glances at the door", "Oshiro steps out and is startled to find Hakobukun right there", "The next night, Oshiro comes out holding a buzzing phone, and Hakobukun is waiting"], "body": "<p>■ It had arrived, and nobody knew<br>Night in the emergency department. Hakobukun brings urgent medicine and stops outside the treatment room: \"I have arrived.\" Inside, Oshiro and Dr. Tanaka are busy with a patient, and no one is watching the corridor. \"Is the medicine here yet?\" When Oshiro steps out, Hakobukun is right there. \"When did you get here?!\"</p><p></p><p>■ Build the alert by testing it on site<br>In <a href=\"https://www.cgh.com.sg/about-cgh/overview/cgh90thanniversary/care-grows-here/engineering-care-together\">an article from Changi General Hospital in Singapore</a>, the team reworked the arrival alerts of a medication delivery robot in the emergency department through prototyping and repeated testing, so that nurses and pharmacists would be notified promptly.</p><p></p><p>■ Conclusion: Decide whose device gets the alert<br>Shiraishi set it up so the arrival alert reaches the phone Oshiro carries and the pharmacy. The next night the phone buzzed and Oshiro came straight out. \"I delivered the news that I arrived.\"<br>A delivery is done when the person receiving it knows.</p>"}
```

#### ページのデータ（英語）ep152.json

```json
{"slug": "ep152", "title": "Before Trusting a Robot With Time-Critical Items, Measure It", "label": "MANGA EP152EN", "character": "ハコブくん", "description": "Terashima hands blood samples to Hakobukun. At noon the lab calls: they haven't arrived. Hakobukun is lost by the elevators on another floor. A manga on confirming delivery times before trusting a robot.", "published": "2026-11-14T01:59:00.000Z", "cover": "/media/manga_ep152en_cover.webp", "comic": "", "cover_alt": "Episode 152 cover: Will it make it in time? Hakobukun looks lost in front of the elevators", "comic_alt": "", "format": "vertical", "panels": ["/media/manga_ep152en_p1.webp", "/media/manga_ep152en_p2.webp", "/media/manga_ep152en_p3.webp", "/media/manga_ep152en_p4.webp"], "panel_alts": ["Terashima places a rack of blood sample tubes in Hakobukun's drawer", "A call from the lab; Terashima goes pale", "Hakobukun is lost in front of the elevators on another floor", "Terashima with a hand on her chest, Hakobukun, and Shiraishi holding a stopwatch"], "body": "<p>■ The samples haven't arrived<br>Morning blood draws. Terashima loads the samples into Hakobukun: \"To the lab, please.\" At noon the lab calls: \"The morning samples haven't arrived yet.\" Hakobukun is on another floor, checking its current location. The samples were just barely usable, but Terashima shakes her head. \"We only made it by luck.\"</p><p></p><p>■ One delay, and the trust is gone<br><a href=\"https://www.proofnews.org/moxi/\">A report on a nursing robot in U.S. hospitals (Proof News)</a> says nurses stopped sending blood draws with the robot because it got lost moving between floors. Late samples have to be discarded and drawn again.</p><p></p><p>■ Conclusion: Measure that it arrives in time, then trust it<br>Shiraishi asked the lab how many minutes each kind of sample can wait, and timed Hakobukun dozens of times. Until that is confirmed, people carry the samples. \"I'll carry them once I've measured that I'm on time.\"</p>"}
```


## SNS を予約した

| 話 | 日本語の X（日本時間） | 英語の X（米国東部時間） |
|---|---|---|
| 148 | 12/14 20:00 | 2/15 11:00 |
| 149 | 12/15 20:00 | 2/16 11:00 |
| 150 | 12/16 20:00 | 2/17 11:00 |
| 151 | 12/19 19:30 | 2/18 11:00 |
| 152 | 12/20 19:30 | 2/19 11:00 |

日本語は、前の話（第143〜147話）と同じく平日の 20:00 に入れました。12/17・12/18 の夜は記事の投稿で埋まっていたので、151・152 は週末の 19:30 にしています。マンガは LinkedIn に出さない決まりなので、LinkedIn の文は json に作っただけで予定表には入れていません。r2r2r-content への commit は `d295dd4` です。

```text
commit d295dd4188be02aa46b34d988c7e780c093aadbe
Author: PixelstoMotors <studio@adaptdesign.jp>
Date:   Sat Nov 14 10:48:59 2026 +0900

    4コマ第148〜152話のSNS（日本語X 12/14〜12/20・英語X 2/15〜2/19）とショートのXの型
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01LevEXTjc44BADYdkHD3BY8

 sns/MANGA_ep148_sns_posts.json | 29 +++++++++++++++++++
 sns/MANGA_ep149_sns_posts.json | 29 +++++++++++++++++++
 sns/MANGA_ep150_sns_posts.json | 29 +++++++++++++++++++
 sns/MANGA_ep151_sns_posts.json | 29 +++++++++++++++++++
 sns/MANGA_ep152_sns_posts.json | 29 +++++++++++++++++++
 sns/SHORT_ep148_sns_posts.json | 13 +++++++++
 sns/SHORT_ep149_sns_posts.json | 13 +++++++++
 sns/SHORT_ep150_sns_posts.json | 13 +++++++++
 sns/SHORT_ep151_sns_posts.json | 13 +++++++++
 sns/SHORT_ep152_sns_posts.json | 13 +++++++++
 sns/schedule.json              | 65 ++++++++++++++++++++++++++++++++++++++++++
```

```diff
diff --git a/sns/schedule.json b/sns/schedule.json
index 6a12b46..e18c087 100644
--- a/sns/schedule.json
+++ b/sns/schedule.json
@@ -3583,6 +3583,71 @@
       "source": "sns/LOG507_sns_posts.json",
       "platform": "x",
       "post_at": "2027-01-02T12:30:00+09:00"
+    },
+    {
+      "id": "MANGA_ep148-x",
+      "source": "sns/MANGA_ep148_sns_posts.json",
+      "platform": "x",
+      "post_at": "2026-12-14T20:00:00+09:00"
+    },
+    {
+      "id": "MANGA_ep148-x-en",
+      "source": "sns/MANGA_ep148_sns_posts.json",
+      "platform": "x",
+      "lang": "en",
+      "post_at": "2027-02-15T11:00:00-05:00"
+    },
+    {
+      "id": "MANGA_ep149-x",
+      "source": "sns/MANGA_ep149_sns_posts.json",
+      "platform": "x",
+      "post_at": "2026-12-15T20:00:00+09:00"
+    },
+    {
+      "id": "MANGA_ep149-x-en",
+      "source": "sns/MANGA_ep149_sns_posts.json",
+      "platform": "x",
+      "lang": "en",
+      "post_at": "2027-02-16T11:00:00-05:00"
+    },
+    {
+      "id": "MANGA_ep150-x",
+      "source": "sns/MANGA_ep150_sns_posts.json",
+      "platform": "x",
+      "post_at": "2026-12-16T20:00:00+09:00"
+    },
+    {
+      "id": "MANGA_ep150-x-en",
+      "source": "sns/MANGA_ep150_sns_posts.json",
+      "platform": "x",
+      "lang": "en",
+      "post_at": "2027-02-17T11:00:00-05:00"
+    },
+    {
+      "id": "MANGA_ep151-x",
+      "source": "sns/MANGA_ep151_sns_posts.json",
+      "platform": "x",
+      "post_at": "2026-12-19T19:30:00+09:00"
+    },
+    {
+      "id": "MANGA_ep151-x-en",
+      "source": "sns/MANGA_ep151_sns_posts.json",
+      "platform": "x",
+      "lang": "en",
+      "post_at": "2027-02-18T11:00:00-05:00"
+    },
+    {
+      "id": "MANGA_ep152-x",
+      "source": "sns/MANGA_ep152_sns_posts.json",
+      "platform": "x",
+      "post_at": "2026-12-20T19:30:00+09:00"
+    },
+    {
+      "id": "MANGA_ep152-x-en",
+      "source": "sns/MANGA_ep152_sns_posts.json",
+      "platform": "x",
+      "lang": "en",
+      "post_at": "2027-02-19T11:00:00-05:00"
     }
   ]
 }
```

#### MANGA_ep148_sns_posts.json

```json
{
 "meta": {
  "episode": 148,
  "date": "2026-11-14",
  "title": "第148話 積む人がいない",
  "url": "https://pixelstomotors.com/manga/ep148",
  "title_en": "Episode 148 Robot Work Includes the People Who Load It",
  "url_en": "https://pixelstomotors.com/english-4-koma/ep148"
 },
 "posts": {
  "x": "リネン庫の前で、ドロワーを開けて待つ配送ロボット。\n\nでもロボットに腕はない。急変でみんな走っていって、1時間たっても空のまま。「誰モ、積ミマセンデシタ」\n4コマ『ハコブくん』第148話\nhttps://pixelstomotors.com/manga/ep148",
  "linkedin": "夕方の病棟。ハコブくんはリネン庫の前でドロワーを開けて、積み込みを待っていた。ところが廊下の奥で急変があって、看護師の大城さんと寺島さんは走っていく。1時間たっても、ドロワーは空のまま。「誰モ、積ミマセンデシタ」。病院で物を運ぶロボット、ハコブくんの4コママンガ（フィクション）の第148話だよ。\n\n元にしたのは、米国の病院で搬送ロボットを調べた研究の紹介記事。ロボットは人が積まないと仕事が始まらず、急な対応の多い病棟では、急な事態に動けない働き手と見られていた。腕のないロボットの仕事には、積む人の手間が最初から入っているんだよね。\n\nマンガでは、エンジニアの白石さんが積み込みの時間と担当を表にする。担当の手がふさがったら、ハコブくんは待ち続けずに別の仕事へ回る。「積ム人ガイナイ時ハ、後デ来マス」。\n\nロボットの予定を組むとき、そばで手を貸す人の予定まで入っていますか？\n\nhttps://pixelstomotors.com/manga/ep148",
  "facebook": "ロボットに腕がないって、こういうことなんだよね。積まれるまで、ただ待つしかない。\n\n急変の日、リネン庫の前で1時間待ちぼうけのハコブくんを描いたよ。積む人の手間まで数に入れて予定を組む話。\n\nhttps://pixelstomotors.com/manga/ep148",
  "x_en": "A delivery robot with no arms, waiting by the linen room.\n\nThen an emergency. An hour later, the drawer is still empty.\n\nHakobukun · Ep. 148\n#webcomic #robotics",
  "x_en_images": [
   "/media/manga_ep148en_p1.webp",
   "/media/manga_ep148en_p2.webp",
   "/media/manga_ep148en_p3.webp",
   "/media/manga_ep148en_p4.webp"
  ],
  "x_en_alts": [
   "Hakobukun waits by the linen room with its drawer open, next to a cart of used linen.",
   "Terashima and Oshiro run to an emergency; Hakobukun keeps waiting behind them.",
   "An hour later Hakobukun is still there; Terashima apologizes.",
   "Shiraishi holds a loading schedule as Hakobukun heads to its next job."
  ],
  "x_en_reply": "Read it with the design note behind the gag: https://pixelstomotors.com/english-4-koma/ep148"
 }
}
```

#### MANGA_ep149_sns_posts.json

```json
{
 "meta": {
  "episode": 149,
  "date": "2026-11-14",
  "title": "第149話 呼ばれなくなった",
  "url": "https://pixelstomotors.com/manga/ep149",
  "title_en": "Episode 149 When People Can't Rely on a Robot, They Quietly Do It Themselves",
  "url_en": "https://pixelstomotors.com/english-4-koma/ep149"
 },
 "posts": {
  "x": "故障の報告はゼロ。なのに、配送の依頼もほぼゼロ。\n\n「この前2回遅れたから、自分で行ったほうが確実なの」\nロボットは、壊れなくても使われなくなる。4コマ『ハコブくん』第149話\nhttps://pixelstomotors.com/manga/ep149",
  "linkedin": "エンジニアの白石さんが記録を見ると、ここ数日、ハコブくんへの配送の依頼がほとんどない。病棟へ行くと、看護師の寺島さんが自分で薬剤部へ向かうところだった。「この前2回遅れたから、自分で行ったほうが確実なの」。故障の報告は1件もない。ただ、使われなくなっていた。病院で物を運ぶロボット、ハコブくんの4コママンガ（フィクション）の第149話だよ。\n\n元にしたのは、デンマークの病院の搬送ロボットの研究。厨房スタッフ16人のうち8人が、ロボットを当てにできないと感じて、ロボットに任せるはずの仕事を自分でやっていた。苦情が出ないまま、静かに使われなくなる。これがいちばん気づきにくい。\n\nマンガでは、白石さんが頼まれた数と、人が自分で運んだ数を並べて毎日見えるようにする。遅れた2回の原因を直して見せると、寺島さんは「直ったなら、もう一回頼んでみる」。\n\n導入したロボット、壊れていない数と、頼られている数を別々に見ていますか？\n\nhttps://pixelstomotors.com/manga/ep149",
  "facebook": "故障もしてないのに、呼ばれなくなるロボット。ちょっとさみしい。\n\n2回遅れただけで「自分で行ったほうが確実」になる。病院の研究でも、半分の人がロボットを当てにせず自分でやっていたそう。\n\n「ワタシハ、呼バレテイマセン」のハコブくん、描いていて切なかった。\n\nhttps://pixelstomotors.com/manga/ep149",
  "x_en": "No fault reports. Almost no delivery requests either.\n\n\"It was late twice, so I just go myself.\"\n\nHakobukun · Ep. 149\n#webcomic #robotics",
  "x_en_images": [
   "/media/manga_ep149en_p1.webp",
   "/media/manga_ep149en_p2.webp",
   "/media/manga_ep149en_p3.webp",
   "/media/manga_ep149en_p4.webp"
  ],
  "x_en_alts": [
   "Shiraishi sees delivery requests falling to almost nothing on a tablet.",
   "Terashima walks to the pharmacy herself, carrying a paper bag.",
   "Hakobukun waits on its charging dock while Shiraishi thinks.",
   "Shiraishi shows Terashima the numbers; Hakobukun looks hopeful."
  ],
  "x_en_reply": "Read it with the design note behind the gag: https://pixelstomotors.com/english-4-koma/ep149"
 }
}
```

#### MANGA_ep150_sns_posts.json

```json
{
 "meta": {
  "episode": 150,
  "date": "2026-11-14",
  "title": "第150話 7つのはずが28",
  "url": "https://pixelstomotors.com/manga/ep150",
  "title_en": "Episode 150 Count the Steps on the Floor, Not in the Manual",
  "url_en": "https://pixelstomotors.com/english-4-koma/ep150"
 },
 "posts": {
  "x": "「検体を運ぶ手順は、7つです」\n\n一日、ロボットの横で見ていたら、拭いて、貼って、押し直して…数えたら28個あった。\n4コマ『ハコブくん』第150話\nhttps://pixelstomotors.com/manga/ep150",
  "linkedin": "エンジニアの白石さんが、検体を運ぶ手順をホワイトボードに書いた。呼ぶ・積む・送る・届く・知らせる・受け取る・戻る。「7つです」。翌日、一日ハコブくんの横で見ていると、看護師の大城さんはドロワーを拭き、伝票をテープで留め、行き先を押し直していた。夕方、メモ帳は手順でびっしり。28個あった。病院で物を運ぶロボット、ハコブくんの4コママンガ（フィクション）の第150話だよ。\n\n元にしたのは、病院の搬送ロボットの運用を観察した報告。管理側が説明した手順は7段階だったのに、実際の運用を見ると28段階あった。差の分は、周りの人が黙って支えている手間だった。\n\n大城さんの「いつもやってるから、手順だと思ってなかった」が、たぶん現場のいちばん正直な答え。効果を測る前に、まず現場で数える。\n\nロボットの導入で、手順書と現場の手順を比べたことはありますか？\n\nhttps://pixelstomotors.com/manga/ep150",
  "facebook": "手順は7つのはずが、現場で数えたら28個。\n\nいつもやっていることほど、本人には手順に見えないんだよね。導入の効果を測る前に、まず横で一日見てみる話を描いたよ。\n\nhttps://pixelstomotors.com/manga/ep150",
  "x_en": "\"Carrying a specimen takes 7 steps.\"\n\nThen she watched for a whole day. There were 28.\n\nHakobukun · Ep. 150\n#webcomic #robotics",
  "x_en_images": [
   "/media/manga_ep150en_p1.webp",
   "/media/manga_ep150en_p2.webp",
   "/media/manga_ep150en_p3.webp",
   "/media/manga_ep150en_p4.webp"
  ],
  "x_en_alts": [
   "Shiraishi points at seven steps on the whiteboard.",
   "Shiraishi takes notes as Oshiro wipes Hakobukun's drawer.",
   "Shiraishi turns pale over a memo pad crammed with notes.",
   "Shiraishi with a long strip of notes, Hakobukun, and a smiling Oshiro."
  ],
  "x_en_reply": "Read it with the design note behind the gag: https://pixelstomotors.com/english-4-koma/ep150"
 }
}
```

#### MANGA_ep151_sns_posts.json

```json
{
 "meta": {
  "episode": 151,
  "date": "2026-11-14",
  "title": "第151話 着いたのに",
  "url": "https://pixelstomotors.com/manga/ep151",
  "title_en": "Episode 151 Arriving Means Nothing Until Someone Knows",
  "url_en": "https://pixelstomotors.com/english-4-koma/ep151"
 },
 "posts": {
  "x": "夜の救急外来。急ぎの薬を運んだロボットは、ちゃんと着いていた。\n\n扉の向こうは処置の真っ最中で、誰も廊下を見ていないだけで。「いつからいたの！」\n4コマ『ハコブくん』第151話\nhttps://pixelstomotors.com/manga/ep151",
  "linkedin": "夜の救急外来。ハコブくんは急ぎの薬を運んで、処置室の前で「到着シマシタ」。でも中では看護師の大城さんと田中先生が処置の最中で、廊下を見ている人はいない。「薬は、まだか？」。大城さんが廊下に出ると、ハコブくんはそこにいた。「いつからいたの！」。病院で物を運ぶロボット、ハコブくんの4コママンガ（フィクション）の第151話だよ。\n\n元にしたのは、シンガポールのチャンギ総合病院の記事。救急外来で薬を運ぶロボットの到着が、看護師と薬剤師にすぐ伝わるよう、試作と試験を繰り返して知らせ方を作り直している。\n\nマンガでは、到着の知らせが大城さんの持つ端末と薬剤部の両方に届くようにする。次の夜、端末が震えて大城さんがすぐ出てくる。「届イタコトヲ、届ケマシタ」。着いたことは、受け取る人に伝わって初めて意味を持つ。\n\nロボットの「完了」、誰に伝わったところで数えていますか？\n\nhttps://pixelstomotors.com/manga/ep151",
  "facebook": "ちゃんと着いてたのに、誰も気づいていなかったハコブくん。\n\n「いつからいたの！」「少シ前カラ」。着いたことを受け取る人に届けるまでが仕事、という話を描いたよ。\n\nhttps://pixelstomotors.com/manga/ep151",
  "x_en": "The robot arrived on time.\n\nNobody knew. Everyone was busy behind the door.\n\nHakobukun · Ep. 151\n#webcomic #robotics",
  "x_en_images": [
   "/media/manga_ep151en_p1.webp",
   "/media/manga_ep151en_p2.webp",
   "/media/manga_ep151en_p3.webp",
   "/media/manga_ep151en_p4.webp"
  ],
  "x_en_alts": [
   "Hakobukun announces its arrival outside a closed treatment room door.",
   "Inside, Dr. Tanaka asks if the medicine is here yet.",
   "Oshiro steps out and is startled to find Hakobukun right there.",
   "Next night, Oshiro comes out holding a buzzing phone."
  ],
  "x_en_reply": "Read it with the design note behind the gag: https://pixelstomotors.com/english-4-koma/ep151"
 }
}
```

#### MANGA_ep152_sns_posts.json

```json
{
 "meta": {
  "episode": 152,
  "date": "2026-11-14",
  "title": "第152話 間に合うかどうか",
  "url": "https://pixelstomotors.com/manga/ep152",
  "title_en": "Episode 152 Before Trusting a Robot With Time-Critical Items, Measure It",
  "url_en": "https://pixelstomotors.com/english-4-koma/ep152"
 },
 "posts": {
  "x": "採血の検体をロボットに託した昼、検査室から電話。「まだ届いてないんですけど」\n\nロボットは別の階で、現在地を確かめていた。\n4コマ『ハコブくん』第152話\nhttps://pixelstomotors.com/manga/ep152",
  "linkedin": "朝の採血。看護師の寺島さんは採った血液をハコブくんに載せた。「検査室へ、お願いね」。昼、検査室から電話。「午前の検体が、まだ届いてないんですけど」。ハコブくんは別の階のエレベーターの前で、現在地を確かめていた。病院で物を運ぶロボット、ハコブくんの4コママンガ（フィクション）の第152話だよ。\n\n元にしたのは、米国の病院の看護ロボットの報道。ロボットが階をまたぐと迷うため、看護師が採血の検体を任せなくなった。遅れた検体は捨てて、患者さんから採り直しになるから。遅れの代償が大きい物ほど、一度の遅れで信頼が戻らない。\n\nマンガでは、エンジニアの白石さんが検体ごとに何分以内に着けばよいかを検査室に聞き、ハコブくんで何十回も走って測る。確かめ終わるまでは人が運ぶ。「間ニ合ウト、測レテカラ運ビマス」。\n\n遅れが許されない物、ロボットに任せる前に何を確かめますか？\n\nhttps://pixelstomotors.com/manga/ep152",
  "facebook": "迷子のハコブくん。エレベーターの前で「現在地ヲ、確カメテイマス」。\n\n検体は時間との勝負。今回は間に合ったけど、運がよかっただけ。任せる前に、間に合うかを測る話を描いたよ。\n\nhttps://pixelstomotors.com/manga/ep152",
  "x_en": "Blood samples, a robot, and a call from the lab:\n\"They haven't arrived yet.\"\n\nHakobukun · Ep. 152\n#webcomic #robotics",
  "x_en_images": [
   "/media/manga_ep152en_p1.webp",
   "/media/manga_ep152en_p2.webp",
   "/media/manga_ep152en_p3.webp",
   "/media/manga_ep152en_p4.webp"
  ],
  "x_en_alts": [
   "Terashima puts blood sample tubes into Hakobukun's drawer.",
   "A call from the lab: the samples haven't arrived. Terashima goes pale.",
   "Hakobukun is lost in front of the elevators on another floor.",
   "Terashima, Hakobukun, and Shiraishi holding a stopwatch."
  ],
  "x_en_reply": "Read it with the design note behind the gag: https://pixelstomotors.com/english-4-koma/ep152"
 }
}
```

#### SHORT_ep148〜152_sns_posts.json（ショートの X の型）

```json
{
  "meta": {
    "kind": "youtube_short",
    "episode": 148,
    "title": "ショート 第148話 積む人がいない",
    "youtube_title": "待っていても、積む人が来ない #病院 #ロボット",
    "manga_url": "https://pixelstomotors.com/manga/ep148"
  },
  "posts": {
    "x_template": "ドロワーを開けて待つ配送ロボット。急変でみんな走っていって、1時間たっても空のまま。\n\n腕がないので、ひたすら待つしかありません（笑） 4コマ『ハコブくん』のショート\n\n{url}\n#病院 #ロボット"
  },
  "note": "YouTube のショートの X 投稿。url と posts.x は tools/sync_youtube_shorts.py が YouTube の公開予約を見て入れる。縦長4コマ（2026-11-14）"
}
```

```json
{
  "meta": {
    "kind": "youtube_short",
    "episode": 149,
    "title": "ショート 第149話 呼ばれなくなった",
    "youtube_title": "故障はないのに、ロボットが呼ばれなくなった #病院 #ロボット",
    "manga_url": "https://pixelstomotors.com/manga/ep149"
  },
  "posts": {
    "x_template": "故障の報告はゼロ。なのに依頼もほぼゼロ。\n\n「この前2回遅れたから、自分で行ったほうが確実なの」 4コマ『ハコブくん』のショート\n\n{url}\n#病院 #ロボット"
  },
  "note": "YouTube のショートの X 投稿。url と posts.x は tools/sync_youtube_shorts.py が YouTube の公開予約を見て入れる。縦長4コマ（2026-11-14）"
}
```

```json
{
  "meta": {
    "kind": "youtube_short",
    "episode": 150,
    "title": "ショート 第150話 7つのはずが28",
    "youtube_title": "手順は7つのはずが、数えたら28 #病院 #ロボット",
    "manga_url": "https://pixelstomotors.com/manga/ep150"
  },
  "posts": {
    "x_template": "「手順は7つです」→ 一日横で見たら28個。\n\n数えるロボットの画面もパンクしました 4コマ『ハコブくん』のショート\n\n{url}\n#病院 #ロボット"
  },
  "note": "YouTube のショートの X 投稿。url と posts.x は tools/sync_youtube_shorts.py が YouTube の公開予約を見て入れる。縦長4コマ（2026-11-14）"
}
```

```json
{
  "meta": {
    "kind": "youtube_short",
    "episode": 151,
    "title": "ショート 第151話 着いたのに",
    "youtube_title": "ロボットは着いたのに、誰も知らない #病院 #ロボット",
    "manga_url": "https://pixelstomotors.com/manga/ep151"
  },
  "posts": {
    "x_template": "急ぎの薬を運んだロボット、ちゃんと着いてたのに誰も気づかない。\n\n「いつからいたの！」 4コマ『ハコブくん』のショート\n\n{url}\n#病院 #ロボット"
  },
  "note": "YouTube のショートの X 投稿。url と posts.x は tools/sync_youtube_shorts.py が YouTube の公開予約を見て入れる。縦長4コマ（2026-11-14）"
}
```

```json
{
  "meta": {
    "kind": "youtube_short",
    "episode": 152,
    "title": "ショート 第152話 間に合うかどうか",
    "youtube_title": "その検体、ロボットで間に合う？ #病院 #ロボット",
    "manga_url": "https://pixelstomotors.com/manga/ep152"
  },
  "posts": {
    "x_template": "採血の検体を託したら、昼に「まだ届いてない」と電話。\n\nロボットは別の階で迷子中でした 4コマ『ハコブくん』のショート\n\n{url}\n#病院 #ロボット"
  },
  "note": "YouTube のショートの X 投稿。url と posts.x は tools/sync_youtube_shorts.py が YouTube の公開予約を見て入れる。縦長4コマ（2026-11-14）"
}
```


## マンガ5話にかかった費用を、1回の頼みごとに調べた

### Gemini の単価と、ドルから円にしたレート

Gemini の費用は、返事の使用量（入力・出力のトークン数）と、Google の[料金のページ](https://ai.google.dev/gemini-api/docs/pricing)の単価から出しました。トークンは、AI が文章や絵を数える単位です。

| 項目 | 値 |
|---|---|
| モデル | gemini-3.1-flash-image（Nano Banana 2） |
| 入力の単価 | 100万トークンあたり $0.50 |
| 出力の画像の単価 | 100万トークンあたり $60（1K の絵1枚は 1,120 トークンで約 $0.067） |
| 出力の残り（画像でない分）の単価 | 100万トークンあたり $3 |
| バッチ | ふつうの半額 |
| レート | 1ドル＝158.84円（[日本銀行の外国為替市況](https://www.boj.or.jp/statistics/market/forex/fxdaily/fxlist/fx261111.pdf)、2026-11-11 の中心相場。見つかった中でいちばん新しい値） |

### Gemini の費用を出したスクリプト（gemini_cost_148_152.py）

```python
"""第148〜152話で Gemini に頼んだ絵の費用を、返事の使用量から出す（2026-11-14）
単価（Google の料金のページ・gemini-3.1-flash-image・100万トークンあたり）：入力 $0.50、出力の画像 $60、出力の残り（画像でない分）$3。バッチは半額。
レート：1ドル＝158.84円（日本銀行 外国為替市況 2026-11-11 の中心相場）"""
import json, glob, os, collections
V = os.path.expanduser("~/mnt/PixelstoMotors/ハコブくん/4コママンガ作成データ/縦長")
R = 158.84
rows = []
for e in range(148, 153):
    for f in sorted(glob.glob(f"{V}/第{e}話/生成/第{e}話_P*_20261114_*.json")):
        u = json.load(open(f))["response"].get("usageMetadata", {})
        img = {d["modality"]: d["tokenCount"] for d in u.get("candidatesTokensDetails", [])}.get("IMAGE", 0)
        inp = u["promptTokenCount"]; rest = u["candidatesTokenCount"] - img
        batch = ("b1" in f) or ("b2" in f)
        usd = (inp * 0.5e-6 + img * 60e-6 + rest * 3e-6) * (0.5 if batch else 1)
        rows.append((os.path.basename(f), "batch" if batch else "std", inp, img, rest, round(usd, 4), round(usd * R, 1)))
for r in rows: print(r)
t = sum(r[5] for r in rows); print(len(rows), "回  $%.4f  約%.0f円" % (t, t * R))
```

### 1回の頼みごとの費用（全64回）

| ファイル（返事の記録） | 頼み方 | 入力 | 出力の画像 | 出力の残り | ドル | 円 |
|---|---|---|---|---|---|---|
| 第148話_P1_20261114_004803b1.json | バッチ | 3,064 | 1,120 | 530 | $0.0352 | 5.6円 |
| 第148話_P1_20261114_004803b2.json | バッチ | 3,064 | 1,120 | 674 | $0.0354 | 5.6円 |
| 第148話_P2_20261114_004803b1.json | バッチ | 4,201 | 1,120 | 404 | $0.0353 | 5.6円 |
| 第148話_P2_20261114_004803b2.json | バッチ | 4,201 | 1,120 | 615 | $0.0356 | 5.7円 |
| 第148話_P3_20261114_004803b1.json | バッチ | 4,440 | 1,120 | 650 | $0.0357 | 5.7円 |
| 第148話_P3_20261114_004803b2.json | バッチ | 4,440 | 1,120 | 599 | $0.0356 | 5.7円 |
| 第148話_P3_20261114_005443.json | ふつう | 4,589 | 1,120 | 785 | $0.0718 | 11.4円 |
| 第148話_P3_20261114_005445.json | ふつう | 4,589 | 1,120 | 703 | $0.0716 | 11.4円 |
| 第148話_P4_20261114_004803b1.json | バッチ | 3,610 | 1,120 | 610 | $0.0354 | 5.6円 |
| 第148話_P4_20261114_004803b2.json | バッチ | 3,610 | 1,120 | 773 | $0.0357 | 5.7円 |
| 第149話_P1_20261114_004803b1.json | バッチ | 1,926 | 1,120 | 454 | $0.0348 | 5.5円 |
| 第149話_P1_20261114_004803b2.json | バッチ | 1,926 | 1,120 | 452 | $0.0348 | 5.5円 |
| 第149話_P2_20261114_004803b1.json | バッチ | 1,971 | 1,120 | 370 | $0.0346 | 5.5円 |
| 第149話_P2_20261114_004803b2.json | バッチ | 1,971 | 1,120 | 414 | $0.0347 | 5.5円 |
| 第149話_P3_20261114_004803b1.json | バッチ | 4,231 | 1,120 | 582 | $0.0355 | 5.6円 |
| 第149話_P3_20261114_004803b2.json | バッチ | 4,231 | 1,120 | 633 | $0.0356 | 5.7円 |
| 第149話_P3_20261114_005449.json | ふつう | 4,348 | 1,120 | 566 | $0.0711 | 11.3円 |
| 第149話_P3_20261114_005457.json | ふつう | 4,348 | 1,120 | 591 | $0.0711 | 11.3円 |
| 第149話_P4_20261114_004803b1.json | バッチ | 4,539 | 1,120 | 578 | $0.0356 | 5.7円 |
| 第149話_P4_20261114_004803b2.json | バッチ | 4,539 | 1,120 | 621 | $0.0357 | 5.7円 |
| 第149話_P4_20261114_012715.json | ふつう | 4,696 | 1,120 | 641 | $0.0715 | 11.4円 |
| 第149話_P4_20261114_012719.json | ふつう | 4,696 | 1,120 | 878 | $0.0722 | 11.5円 |
| 第150話_P1_20261114_004803b1.json | バッチ | 1,933 | 1,120 | 367 | $0.0346 | 5.5円 |
| 第150話_P1_20261114_004803b2.json | バッチ | 1,933 | 1,120 | 385 | $0.0347 | 5.5円 |
| 第150話_P1_20261114_004950.json | ふつう | 2,026 | 1,120 | 518 | $0.0698 | 11.1円 |
| 第150話_P1_20261114_005031.json | ふつう | 2,026 | 6,720 | 2,589 | $0.4120 | 65.4円 |
| 第150話_P2_20261114_004803b1.json | バッチ | 4,389 | 1,120 | 607 | $0.0356 | 5.7円 |
| 第150話_P2_20261114_004803b2.json | バッチ | 4,389 | 1,120 | 486 | $0.0354 | 5.6円 |
| 第150話_P3_20261114_004803b1.json | バッチ | 2,039 | 1,120 | 728 | $0.0352 | 5.6円 |
| 第150話_P3_20261114_004803b2.json | バッチ | 2,039 | 1,120 | 440 | $0.0348 | 5.5円 |
| 第150話_P3_20261114_005448.json | ふつう | 2,088 | 1,120 | 512 | $0.0698 | 11.1円 |
| 第150話_P3_20261114_005451.json | ふつう | 2,088 | 1,120 | 427 | $0.0695 | 11.0円 |
| 第150話_P4_20261114_004803b1.json | バッチ | 4,742 | 1,120 | 503 | $0.0355 | 5.6円 |
| 第150話_P4_20261114_004803b2.json | バッチ | 4,742 | 1,120 | 644 | $0.0358 | 5.7円 |
| 第150話_P4_20261114_005453.json | ふつう | 4,841 | 1,120 | 531 | $0.0712 | 11.3円 |
| 第150話_P4_20261114_005459.json | ふつう | 4,841 | 1,120 | 494 | $0.0711 | 11.3円 |
| 第150話_P4_20261114_012716.json | ふつう | 5,013 | 1,120 | 615 | $0.0716 | 11.4円 |
| 第150話_P4_20261114_012717.json | ふつう | 5,013 | 1,120 | 747 | $0.0719 | 11.4円 |
| 第151話_P1_20261114_004803b1.json | バッチ | 2,763 | 1,120 | 517 | $0.0351 | 5.6円 |
| 第151話_P1_20261114_004803b2.json | バッチ | 2,763 | 1,120 | 550 | $0.0351 | 5.6円 |
| 第151話_P2_20261114_004803b1.json | バッチ | 2,533 | 1,120 | 404 | $0.0348 | 5.5円 |
| 第151話_P2_20261114_004803b2.json | バッチ | 2,533 | 1,120 | 508 | $0.0350 | 5.6円 |
| 第151話_P3_20261114_004803b1.json | バッチ | 4,233 | 1,120 | 524 | $0.0354 | 5.6円 |
| 第151話_P3_20261114_004803b2.json | バッチ | 4,233 | 1,120 | 412 | $0.0353 | 5.6円 |
| 第151話_P3_20261114_005449.json | ふつう | 4,344 | 1,120 | 606 | $0.0712 | 11.3円 |
| 第151話_P3_20261114_005450.json | ふつう | 4,344 | 1,120 | 554 | $0.0710 | 11.3円 |
| 第151話_P4_20261114_004803b1.json | バッチ | 3,710 | 1,120 | 612 | $0.0354 | 5.6円 |
| 第151話_P4_20261114_004803b2.json | バッチ | 3,710 | 1,120 | 645 | $0.0355 | 5.6円 |
| 第151話_P4_20261114_004948.json | ふつう | 3,765 | 1,120 | 506 | $0.0706 | 11.2円 |
| 第151話_P4_20261114_004950.json | ふつう | 3,765 | 1,120 | 683 | $0.0711 | 11.3円 |
| 第152話_P1_20261114_004803b1.json | バッチ | 4,269 | 1,120 | 623 | $0.0356 | 5.7円 |
| 第152話_P1_20261114_004803b2.json | バッチ | 4,269 | 1,120 | 671 | $0.0357 | 5.7円 |
| 第152話_P1_20261114_012717.json | ふつう | 4,424 | 1,120 | 528 | $0.0710 | 11.3円 |
| 第152話_P1_20261114_012718.json | ふつう | 4,424 | 1,120 | 725 | $0.0716 | 11.4円 |
| 第152話_P2_20261114_004803b1.json | バッチ | 1,947 | 1,120 | 456 | $0.0348 | 5.5円 |
| 第152話_P2_20261114_004803b2.json | バッチ | 1,947 | 1,120 | 431 | $0.0347 | 5.5円 |
| 第152話_P3_20261114_004803b1.json | バッチ | 2,719 | 1,120 | 498 | $0.0350 | 5.6円 |
| 第152話_P3_20261114_004803b2.json | バッチ | 2,719 | 1,120 | 458 | $0.0350 | 5.6円 |
| 第152話_P4_20261114_004803b1.json | バッチ | 4,653 | 1,120 | 527 | $0.0356 | 5.6円 |
| 第152話_P4_20261114_004803b2.json | バッチ | 4,653 | 1,120 | 738 | $0.0359 | 5.7円 |
| 第152話_P4_20261114_005450.json | ふつう | 4,765 | 1,120 | 657 | $0.0716 | 11.4円 |
| 第152話_P4_20261114_005451.json | ふつう | 4,765 | 1,120 | 710 | $0.0717 | 11.4円 |
| 第152話_P4_20261114_012716.json | ふつう | 4,937 | 1,120 | 636 | $0.0716 | 11.4円 |
| 第152話_P4_20261114_012725.json | ふつう | 4,937 | 1,120 | 843 | $0.0722 | 11.5円 |
| 合計（64回） | | 235,496 | 77,280 | 38,738 | $3.4605 | 約550円 |

### 段階ごと・話ごとの Gemini の費用

| 段階 | 頼んだ回数 | 描かれた絵 | 費用 |
|---|---|---|---|
| バッチの下書き | 40回 | 40枚 | 224円（$1.411） |
| 自分の確認で描き直し | 4回 | 9枚 | 99円（$0.624） |
| 別のエージェントの確認で描き直し | 12回 | 12枚 | 135円（$0.853） |
| エイイチの指摘で描き直し | 8回 | 8枚 | 91円（$0.574） |
| 表紙の吹き出しを消す編集 | 5回 | 0枚（月の上限で断られた） | 0円 |
| 合計 | 69回 | 69枚 | 550円（$3.461） |

| 話 | 頼んだ回数（断られた分を除く） | 費用 |
|---|---|---|
| 148 | 10回 | 68円 |
| 149 | 12回 | 90円 |
| 150 | 16回 | 189円 |
| 151 | 12回 | 90円 |
| 152 | 14回 | 113円 |

第150話がいちばん高いのは、1コマ目の描き直しの1回で絵が6枚返ってきたためです。その返事の使用量は次のとおりでした。

```
54.1 {'promptTokenCount': 2026, 'candidatesTokenCount': 9309, 'totalTokenCount': 11335, 'cachedContentTokenCount': 544, 'promptTokensDetails': [{'modality': 'TEXT', 'tokenCount': 1510}, {'modality': 'IMAGE', 'tokenCount': 516}], 'cacheTokensDetails': [{'modality': 'TEXT', 'tokenCount': 406}, {'modality': 'IMAGE', 'tokenCount': 138}], 'candidatesTokensDetails': [{'modality': 'IMAGE', 'tokenCount': 6720}], 'serviceTier': 'standard'}
[[['thoughtSignature'], ['thoughtSignature'], ['thoughtSignature'], ['thoughtSignature'], ['thoughtSignature'], ['thoughtSignature']]]
```

出力の画像のトークンが 6,720（1,120 の6倍）で、返事の中に絵の部分が6つありました。かかった時間も54秒で、ふつうの1枚（13〜20秒）より長くかかっています。描く道具（vbuild.py）は返ってきた最後の1枚だけを保存するので、ほかの5枚は見られません。

### 11月の Gemini の支払い

Google から届いた支払いのメールを並べると、11月は次のとおりです。Google AI Studio の支払いは、先にお金を入れて使った分が引かれる形で、残高が800円を下回ると800円を足す（1か月の上限5,000円）設定にしています。

| 日付 | 内容 |
|---|---|
| 11月1日 | 800円を足した |
| 11月4日 | 800円を足した（3回） |
| 11月5日 | 800円を足した |
| 11月7日 | 800円を足した |
| 11月8日 | 「1か月の請求額の上限に達しました」で、足すのが止まった |

11月に足した額は4,800円です。今回の550円は、止まる前に足してあった残高から引かれ、表紙の編集のときに残りが足りなくなりました。次に絵を描く前に、上限を上げる必要があります。

### 声（ElevenLabs）の費用

5話で読ませた文字は、合わせて423字でした。

| 話 | 文字数 |
|---|---|
| 148 | 91字 |
| 149 | 102字 |
| 150 | 93字 |
| 151 | 60字 |
| 152 | 77字 |
| 合計 | 423字 |

ElevenLabs は月額の料金で、1か月に使える文字数（クレジット）が決まっています。[Starter は月6ドルで3万クレジット、Creator は月22ドルで12万1千クレジット](https://dupple.com/pricing/elevenlabs)です（2026年10月の料金のページを読んだ一覧）。1字1クレジットとすると、Starter なら1字 $0.0002、Creator なら1字約 $0.00018 で、423字は約12〜13円です。今使っているプランを API の鍵では読めなかったので、両方の値を書いています。月額そのものは毎月かかる固定の費用で、ここには入れていません。同時に頼みすぎて断られた分には、費用はかかっていません。

### X の API の費用

X の投稿は、リンクなしで1本 $0.015 です（article_rules.md の「X のリンク」）。日本語5本と英語5本の10本で $0.15、約24円です。予約したもので、投稿されるとき（12月と2月）に X のクレジットから引かれます。

### 合計と、前の5話との比べ

| 費用 | 額 |
|---|---|
| Gemini（絵） | 約550円 |
| ElevenLabs（声） | 約13円 |
| X の API（10本） | 約24円 |
| Cloudflare R2（画像50枚・8.3MB） | 0円（無料の枠の中） |
| 合計 | 約590円（1話あたり約120円） |

Claude と ElevenLabs の月額、X のクレジットの残りなど、毎月かかる固定の費用は入れていません。

| 話 | Gemini に描かせた絵 | Gemini の費用 | エイイチの指摘 | 出し方 |
|---|---|---|---|---|
| 第138〜142話（LOG448） | 67枚 | 約460円 | ― | 見積もり |
| 第143〜147話（LOG468） | 120枚 | 約1,100円 | 5回 | 1枚約11円とバッチ半額で見積もり |
| 第148〜152話 | 69枚 | 約550円 | 1回 | 1回ずつの使用量から計算 |

LOG468 では「次の話で、エイイチの指摘の数と描き直しの枚数で確かめる」と書いていました。今回の描き直しは24回（絵29枚）で、前回の80枚の約3分の1です。指摘は5回から1回になりました。ただし、今回は別のエージェントの確認で12回描き直していて、前回はそれがエイイチの指摘のあとに来ていた、という順番の違いもあります。

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


## 次の5話の前に、Gemini の上限と、絵が何枚返ったかを確かめる

次は第153話からです。描く前に、Google AI Studio の月の上限を上げます。描く道具には、1回の返事に絵が何枚入っていたかを記録に残す行を足し、2枚以上返ったときに分かるようにします。費用は今回と同じく、1回ずつの使用量から出して、1話約120円と比べます。
