---
title: "サービスロボット導入シミュレーション Log#23 | 導入前に、配膳ロボットの詰まりを動画で見比べる"
slug: lp-demo-same-time-compare
date: 2026-10-29
day: 404
meta_description: "配膳ロボットのシミュレーション動画は、店の形を直す前と後を同じ客・同じ時刻で並べると違いが伝わる。Unityの3Dのファミレスで丸テーブルの間を1.1mから2.1mに広げ、止まり8→0回の前後を導入前シミュレーションのページに並べた。"
tags: [サービスロボット導入シミュレーション, 配膳ロボット, 飲食店, Unity, シミュレーション, 店舗レイアウト, LP, 動画]
series: "導入シミュレーション"
cover: "/media/LOG404_cover.jpg"
---

配膳ロボットのシミュレーションを動画で見せるなら、ロボットが店の中を走るだけの映像より、店の形を直す前と直した後を、同じお客さん・同じ時刻で左右に並べた映像のほうが、何が変わったのかが一目で伝わります。[Unity](https://unity.com/ja)（ゲームを作る道具）の3Dで再現したファミレスで、丸テーブル2つの間を1.1mから2.1mに広げる前と後を撮り、前はロボット2台が約8分動けず、後は同じ場面を通り抜ける映像を、配膳ロボットの導入前シミュレーションのページ（pixelstomotors.com/serving-robot-one-less-staff ）に並べました。

このシリーズでは、配膳ロボットを店に入れるとどうなるかを、サイトの試算と、Unity で建てた3Dのファミレスの2つで調べています。調べた結果は、飲食店や、ロボットを提案するメーカー・販売会社の人が読む前提で、1枚のページ（LP：ランディングページ。1つの用件のために作る縦長のページ）にまとめています。そこに「Unity のシミュレーションのデモ動画を載せたら効果があるか」を考えたところ、ただ走っているだけの映像は、ゲームの画面のように見えて判断の材料にならない、というのが最初の見立てでした。導入を考える人が知りたいのは、自分の店で何が起きるか、直したら何が変わるか、です。

そこで題材を、前の記事（LOG402）で見つけた「記憶の店（② 定番の郊外型）」の丸テーブルにしました。丸テーブル2つの間が1.1mしかなく、1台が卓に止まると、もう1台が抜けられずに動けなくなっていた所です。右の丸テーブルを1.0m右へ寄せると、60秒以上動けなくなる回数は8回から0回になりました。この「直す前」と「直した後」を、お客さんの来方を決める番号（種）を同じにして2回走らせ、同じ時刻を同じ画角の真上から撮りました。違うのは右の丸テーブルの位置だけです。

**同じ客・同じ時刻で撮ると、左右の違いは店の形のせいだと言い切れます。** 録画した場面のロボットの位置は、画面なしで回した記録と1行も違いませんでした。録画するために動きが変わった、ということもありません。

| やったこと | なぜ | 結果 |
|---|---|---|
| デモ動画をどこに・何を載せるかを考えた | 走るだけの映像では判断の材料にならない | LP の「どこで止まるか」に、前後を並べた動画を足すと決めた |
| ②の丸テーブルの前後を、同じ種・同じ時刻で録画した（依頼26） | 違いを店の形だけにするため | 前は2台が約8分動けない。後は通り抜ける。位置は画面なしの回と同じ |
| 並べる形と切り替える形を、確認用のページで見比べた | どちらが伝わるかは見て決める | 並べる形に決めた |
| 光の角度を52°から75°に上げて撮り直した（依頼28） | 斜めの長い影が、客や椅子より目立っていた | 影が短くなった。動きは変わらない |
| LP に並べ、PC とスマホで確かめて公開した | 実際の画面で崩れないか | 公開後に2本が0.67秒ずれていたので、そろえて流す作りに直した |

ここから下は作業ログです。デモ動画を載せる場所の考え方、Claude Code に頼んだ録画の依頼文と報告、確認用のページ、影の撮り直し、LP の差分と確かめまでを、コード・依頼文・報告・出力とあわせて載せます。効果のところは、公開した直後なので見込みで書いています（何の数字で確かめるかを添えています）。

配膳ロボットを自分の店に入れたらどうなるかは、このシリーズの試算をもとにした別のページで、この動画も含めて見られます。

【バナー】serving-robot-one-less-staff


## 作業ログ ― デモ動画は、走る様子より直す前と後を見せると決めた

【ファイル一式】LOG404


### LP（ランディングページ）は、導入を考える人が読む1枚のページ

LP は、1つの用件のために作る縦長の1枚のページです。このサイトの配膳ロボットの LP（/serving-robot-one-less-staff）は、飲食店の経営者・責任者と、ロボットを提案するメーカー・販売会社の人を読み手にして、「導入前に、自分の店で何人で回るか・どこで止まるか・月いくらかかるか」を試算と3Dの店で確かめられることを伝えています。ページの最後のボタンは、自分の店の条件で試算を相談するメッセージフォームにつながります。

### 載せる前のサイトの状態：LP には Unity の短い動画が4本あった

デモ動画を足す前に、サイトのコード（GitHub の p2m-site）を読んで、どこに何があるかを確かめました。

| ページ | Unity の映像 | 見立て |
|---|---|---|
| 配膳ロボットの LP | 最初の画面に3本（斜め・真上の混んだ時間・試算のコード）、「どこで止まるか」に4つの視点の1本 | 走っている様子はもうある。直す前と後を並べた映像はない |
| /series8（シリーズの一覧） | LP へのバナーの絵（立体の図）だけ | 動画を入れる余地はあるが、記録の一覧から LP へ人を送る役目 |
| トップ | いちばん上はハコブくんの動画 | ここに入れると、1つの画面で伝えることが2つになる |

そのうえで、載せる場所ごとの効き方を次のように見立てました。

| 載せる場所 | 効き方の見立て | 理由 |
|---|---|---|
| LP の「どこで止まるか」に、前後を並べた動画 | 高い | 79→0回・112→31回の数字に、目で見える証拠が付く |
| /series8 のバナーの絵を Unity のループ動画に | 中 | 一覧から LP へ進む人を増やせる |
| 1〜2分の通しのデモ（YouTube に置いて埋め込む） | 中 | チャンネルの方向（入れたらどうなるかを数字とシミュレーションで確かめる）と合う |
| トップの一番上 | 低い | ハコブくんとぶつかる |

気をつけることも決めました。映像の中に入店待ちや必要な人数の数字を焼き込まないこと（そこはサイトの試算が正で、Unity の数字と並べるとずれて見える）。映像で扱うのは、Unity が測る側の「止まる場所と回数」だけにすること。キッチンのモニターの文字がスマホで読めない大きさなら、寄りで撮ること。暗い膜をかけた映像や、画質の悪い映像は使わないこと。

- ねらい：LP にある「どこで止まるか」の数字に、目で見える証拠を付ける
- 変わったこと：数字の欄の下に、同じ店・同じ時刻の直す前と直した後の動画が並ぶ
- 見込める効果：メーカー・販売会社の人が、提案先にそのまま見せられる材料になり、相談のきっかけになる（見込み。公開後に、サイトの使われ方の記録 /api/ev で、この節まで読まれた割合と相談のボタンが押された数を見て確かめる）

### 題材は、前の記事で見つけた②の丸テーブルにした

前の記事（LOG402）で、記憶の店（② 定番の郊外型・19卓・88席）を Unity に建てたとき、丸テーブル T05・T06 の間（1.1m）で、ロボット2台の回に60秒以上動けなくなるのが8回出ました。右の丸テーブル T06 を右へ1.0m寄せて間を2.1mにすると、0回になりました。

LP にもともと載っていた 79→0回（細い通路の入口の鉢合わせ）と 112→31回（レジのまわり）は、ロボットを決まった線の上だけ走らせていた頃の古い作りの記録で、録画も残っていません。今の作りで同じ条件を撮り直せる題材として、店の形を1か所直しただけで止まりが消えた②の丸テーブルを選びました。店の側が自分で打てる手（卓を動かす）なので、LP の「自分の店の形で結果が変わる」という話にも合います。

### 丸テーブルのまわりの止まりを、回ごとにまとめた round_zone_stops.py

どの時刻を撮ればよいかを決めるために、丸テーブルのまわり（x 4.75〜6.85・y 11.3〜14.0）の止まりを、回ごと・ロボットと往復ごとにまとめるスクリプトを書きました。画面なしで回した直す前（p0check）と直した後（p0fixcheck）に加えて、あとで録画した4回も同じスクリプトで並べています。

```python
"""②の丸テーブルのまわり（x 4.75〜6.85・y 11.3〜14.0）で起きた止まりを、回ごと・ロボットと往復ごとにまとめる（LOG404）。
録画にする場面を選ぶために、直す前（p0check）と直した後（p0fixcheck）の画面なしの回と、録画した回を並べる。"""
import csv, sys
from pathlib import Path

U = Path(sys.argv[1])
RUNS = [
    ("直す前・画面なし", "20261029_p0check_cs39_fam_p0_r2_s0_passon_sbret"),
    ("直した後・画面なし", "20261029_p0fixcheck_cs40_fam_p0_r2_s0_passon_sbret"),
    ("直す前・録画（52°）", "20261029_lpdemobefore_cs41_fam_p0_r2_s0_passon_sbret"),
    ("直した後・録画（52°）", "20261029_lpdemo_cs41_fam_p0_r2_s0_passon_sbret"),
    ("直す前・録画（75°）", "20261029_lpdemo2before_cs43_fam_p0_r2_s0_passon_sbret"),
    ("直した後・録画（75°）", "20261029_lpdemo2_cs43_fam_p0_r2_s0_passon_sbret"),
]
X0, X1, Y0, Y1 = 4.75, 6.85, 11.3, 14.0

for label, stem in RUNS:
    rows = list(csv.DictReader(open(U / stem / "stops.csv", encoding="utf-8")))
    z = [r for r in rows if X0 <= float(r["x"]) <= X1 and Y0 <= float(r["z"]) <= Y1]
    stuck = [r for r in z if float(r["duration_s"] or 0) >= 60]
    print(f"== {label}  {stem}")
    print(f"   止まりの行 全体 {len(rows)}・丸テーブルのまわり {len(z)}・そのうち60秒以上 {len(stuck)}")
    eps = {}
    for r in z:
        k = (r["robot_id"], r["trip_id"])
        e = eps.setdefault(k, {"t0": float(r["t_s"]), "t1": float(r["t_s"]), "n": 0, "dur": 0.0, "causes": {}})
        t = float(r["t_s"]); d = float(r["duration_s"] or 0)
        e["t0"] = min(e["t0"], t); e["t1"] = max(e["t1"], t + d); e["n"] += 1; e["dur"] += d
        c = r["cause"]; e["causes"][c] = e["causes"].get(c, 0) + 1
    for (rid, tid), e in sorted(eps.items(), key=lambda kv: kv[1]["t0"]):
        if 900 <= e["t0"] <= 1650 or e["dur"] >= 60:
            cs = "・".join(f"{c} {n}" for c, n in sorted(e["causes"].items(), key=lambda x: -x[1]))
            print(f"   ロボット{rid} 往復{tid:>3}  {e['t0']:7.1f}〜{e['t1']:7.1f} 秒  行 {e['n']:3}  止まりの合計 {e['dur']:6.1f} 秒  {cs}")
```

### round_zone_stops.py の出力

```
== 直す前・画面なし  20261029_p0check_cs39_fam_p0_r2_s0_passon_sbret
   止まりの行 全体 739・丸テーブルのまわり 158・そのうち60秒以上 8
   ロボット1 往復  3    948.1〜 1327.0 秒  行   3  止まりの合計  180.1 秒  椅子・荷物・ベビーカー 3
   ロボット2 往復  4   1021.2〜 1571.3 秒  行 130  止まりの合計  414.7 秒  もう1台のロボット 121・椅子・荷物・ベビーカー 5・スタッフとすれ違った 3・人が立ち止まっていた 1
   ロボット1 往復  6   1629.7〜 1630.7 秒  行   1  止まりの合計    1.0 秒  椅子・荷物・ベビーカー 1
== 直した後・画面なし  20261029_p0fixcheck_cs40_fam_p0_r2_s0_passon_sbret
   止まりの行 全体 510・丸テーブルのまわり 6・そのうち60秒以上 0
   ロボット1 往復  3    975.9〜  979.9 秒  行   1  止まりの合計    4.0 秒  椅子・荷物・ベビーカー 1
== 直す前・録画（52°）  20261029_lpdemobefore_cs41_fam_p0_r2_s0_passon_sbret
   止まりの行 全体 782・丸テーブルのまわり 167・そのうち60秒以上 8
   ロボット1 往復  3    948.1〜 1327.0 秒  行   3  止まりの合計  180.1 秒  椅子・荷物・ベビーカー 3
   ロボット2 往復  4   1021.2〜 1571.3 秒  行 130  止まりの合計  414.7 秒  もう1台のロボット 121・椅子・荷物・ベビーカー 5・スタッフとすれ違った 3・人が立ち止まっていた 1
   ロボット1 往復  6   1629.7〜 1630.7 秒  行   1  止まりの合計    1.0 秒  椅子・荷物・ベビーカー 1
== 直した後・録画（52°）  20261029_lpdemo_cs41_fam_p0_r2_s0_passon_sbret
   止まりの行 全体 454・丸テーブルのまわり 8・そのうち60秒以上 0
   ロボット1 往復  3    975.9〜  979.9 秒  行   1  止まりの合計    4.0 秒  椅子・荷物・ベビーカー 1
== 直す前・録画（75°）  20261029_lpdemo2before_cs43_fam_p0_r2_s0_passon_sbret
   止まりの行 全体 700・丸テーブルのまわり 153・そのうち60秒以上 8
   ロボット1 往復  3    948.1〜 1327.0 秒  行   3  止まりの合計  180.1 秒  椅子・荷物・ベビーカー 3
   ロボット2 往復  4   1021.2〜 1571.3 秒  行 130  止まりの合計  414.7 秒  もう1台のロボット 121・椅子・荷物・ベビーカー 5・スタッフとすれ違った 3・人が立ち止まっていた 1
   ロボット1 往復  6   1629.7〜 1630.7 秒  行   1  止まりの合計    1.0 秒  椅子・荷物・ベビーカー 1
== 直した後・録画（75°）  20261029_lpdemo2_cs43_fam_p0_r2_s0_passon_sbret
   止まりの行 全体 612・丸テーブルのまわり 5・そのうち60秒以上 0
   ロボット1 往復  3    975.9〜  979.9 秒  行   1  止まりの合計    4.0 秒  椅子・荷物・ベビーカー 1
```

直す前は、ロボット1が T03 へ料理を運ぶ途中、948秒ごろに丸テーブルの間で止まり、1021秒ごろに来たロボット2がその後ろで動けなくなります。ロボット2の止まりは130行・合計414.7秒で、理由のほとんど（121行）が「もう1台のロボット」です。直した後は、同じロボット1が同じ往復で4秒だけ止まって抜けていきます。6回とも、この区間の止まりは同じ時刻・同じ中身でした。

【図解】LOG404_same_time_timeline.html


## 作業ログ ― 丸テーブルの直す前と後を、同じ時刻で録画した（依頼26）

### Unity は、ゲームを作る道具で、3Dの店を動かしている

[Unity](https://unity.com/ja) は、米国の Unity Technologies が作っているゲームエンジン（ゲームを作るための道具）です。3Dの空間に床や壁や人を置き、物理の計算をしながら動かせます。このシリーズでは、ファミレスの店を3Dで建て、お客さん・スタッフ・配膳ロボットを1人ずつ動かして、待ち時間や詰まる場所を数えています。

この記事で使う言葉は、次のとおりです。

| 言葉 | 意味 |
|---|---|
| 種（シード） | お客さんの来方や注文を決める乱数の番号。同じ種なら、同じお客さんが同じ時刻に来る |
| 画面なしの実行ファイル | Unity の画面を開かずに、店を速く回すだけの書き出したプログラム。夜にまとめて回すのに使う |
| cs（チェンジセット） | Unity のプロジェクトの版の番号（Unity Version Control で記録している）。cs:43 のように書く |
| 寄りの映像（映像A） | 丸テーブルのまわりだけを真上から撮った映像。x 3.0〜10.0・y 10.5〜15.0 の範囲 |
| 台帳 | Unity で回した1回ずつを1行で残している表（unity_ledger_v1.csv）。追記だけで、消さない |

### Claude Code と依頼箱で、Mac の Unity を動かしている

Unity と、Mac の中のファイルを触るのは [Claude Code](https://claude.com/claude-code)（Anthropic のプログラミング用の AI）の役目です。Claude（この記事を書いている側）が依頼文を iCloud の「依頼箱」というフォルダに置くと、Mac で待っている小さなプログラムがそれを見つけて Claude Code に渡し、終わると報告を書き戻します（作り方は LOG390）。依頼の頭に、作業してよいフォルダと上限の時間を書きます。

### 依頼文：LP用に店2の直す前と後を録画する（20261029_26）

```markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 150
---
# 依頼：配膳ロボットの LP に載せる「直す前・直した後」の動画の素材として、②の丸テーブルのまわりを同じ条件で2回録画する（2026-10-29）

## ねらい

LP（/serving-robot-one-less-staff）の「どこで止まるか」に、店の形を直すと止まらなくなる様子を、直す前と直した後を並べた動画で見せる（エイイチと決めた）。題材は②の丸テーブル（LOG402）。

- 直す前：店の形の版 dffc100b269c（cs:39 で建てた②）。丸テーブル T05・T06 の間 1.1m
- 直した後：店の形の版 08db8a0d067c（cs:40 で建てた②。T06 を右へ1.0m）。間 2.1m
- 画面なしで回した確かめ（ロボット2台・種0）では、丸テーブルのまわりの60秒の「動けなくなった」が 8 → 0
- 直す前の回（20261029_p0check_cs39_fam_p0_r2_s0_passon_sbret の stops.csv）では、ロボット2 が往復4で、T05 に止まっているロボット1 を待って、x 5.85〜6.14・y 11.86〜11.98 で t=1021 秒ごろから約1527 秒まで動けなかった。これを映したい

録画は見せるための素材で、測る回ではない。店の回し方・動き・設定は何も変えない。

## 始める前に確かめること（違えば手を止めて報告）

1. Unity エディタが起動していない
2. /Volumes/KIOXIA がマウントされている。鍵ファイル unity_ledger_v1.lock がない
3. 台帳 unity_ledger_v1.csv が 667 行、md5 が c7e561fec03ba49cb6cf5fa44628fe5e
4. ServiceSim の保留が 0（cs:40 head）
5. iCloud の unity/site_copy_dffc100b269c/ と unity/site_copy_08db8a0d067c/ がある

## やること

1. 録画のしくみを確かめる。Unity Recorder がこのプロジェクトに入っていなければ、パッケージだけ足して check-in する（足すのは録画のためのものだけ。店・人・ロボットのコードと設定は変えない）。録画用のカメラや撮るための小さなスクリプトを足す場合も、シミュレーションの動きに触れない形にし、足したものは check-in する
2. 直した後（今の cs:40 の②のまま）を録画する：② × ロボット2台 × 種0、今の標準（すれ違い入・待機場所へ戻る 入・案B 切）、ラベル `lpdemo`
3. 直す前を録画する：site_copy_dffc100b269c の②の店の形と params をプロジェクトに写し、cs:39 と同じやり方（JSON Store「② を建てて確かめる」・Map Planner「② 確かめ A」）で②を建て直してから、2 と同じ条件で録画する。直す前の状態は check-in しない。録画が終わったら cm undo で戻し、cs:40 の状態（保留 0）に戻ったことを確かめる（cs:39 と cs:40 はコードが同じで、違うのは params と店の形のデータだけ、という前提。違っていたら手を止めて報告）
4. 録画の中身（2回とも同じ）
   - 撮る範囲の時刻：シミュレーションの時刻で 900 秒〜1650 秒。録画しない時間は今までどおり速く回してよい
   - 撮り方：シミュレーションの時刻 0.2 秒ごとに1コマ（時刻に合わせて撮る。録画の重さで動きが変わらないようにする）。mp4 は 30 コマ/秒で書く（6倍速になる）
   - 映像A（寄り）：真上からの正投影。範囲は x 3.0〜10.0・y 10.5〜15.0（丸テーブル2つ・T01・T04・パントリーの壁が入る）。1680×1080。2回で範囲・向き・明るさをまったく同じにする
   - 映像B（全体）：真上から店全体。横 1920 以上。2回で同じにする
   - 見た目は今の標準のまま（ロボット白・スタッフ青・客黄）。暗い膜・文字の重ね・ぼかしは入れない。画質を落とさない（H.264・CRF 16 前後・yuv420p）
   - 各コマのシミュレーションの時刻の対応表（frame, t_s）を CSV で一緒に置く
5. 保存先：KIOXIA の service_sim/unity/{run_stem}/recordings/（今までの録画と同じ置き方。同じ名前のフォルダがあれば上書きせずに止める）。出力（stops.csv など）もいつもどおり同じ run_stem に置く
6. 台帳は追記だけ（ラベル lpdemo の2行で 667 → 669 行）。先頭 667 行の md5 が上と同じか確かめる

## 確かめること（報告に書く）

- 直す前の録画の回で、丸テーブルのまわり（x 4.75〜6.85・y 11.3〜14.0）の60秒の「動けなくなった」があったか、その時刻。p0check（画面なし）の回と同じ時刻に起きたか（ロボット2・往復4・t=1021〜1527 秒ごろ）
- 直した後の録画の回で、同じ範囲の60秒の「動けなくなった」が 0 か。p0fixcheck（画面なし）の回と同じ動きになったか
- 直した後の回で、片方のロボットが T05 か T06 に止まっている間に、もう1台が2つの丸テーブルの間を通り抜けた時刻（見せたい場面の候補。900〜1650 秒の中で3つまで）
- 直す前の録画で、もう1台が抜けられずに待っている場面の始まりと終わりの時刻
- 録画した回と画面なしの回で、30秒以上の止まりの数・往復の数・入店待ちが大きく違えば、その差（録画が動きを変えていないかを見るため）

直す前の録画で丸テーブルの詰まりが起きなかった場合だけ、種を変えて（種1〜9 で、夜 20261105 の②には直した後しかないので、画面なしで直す前を回して詰まりが出る種を探す）1回まで録り直してよい。そのときは直した後も同じ種で録り直し、2回の種をそろえる。

## 変えないもの

- 店の回し方・人とロボットの動き・速さ・Stiffness(20,000)/Damping(2,000) などの物理の設定
- 台帳の列（59列）と今までの行。今までの night/ と出力の記録・録画
- make_queue.py・night_runner.py・judge_night.py
- サイト（p2m-site）は触らない
- git clean・git add . は使わない

## 報告

依頼箱の結果に加えて、claude_code_reports に `20261029_HHMM_lp_before_after_rec.txt` を書く（場所：~/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity/claude_code_reports/）。

- 始める前の確かめ5つの結果
- 足したもの（Recorder・カメラ・スクリプト）と cs 番号、最後に保留 0 で cs:40 以降の head にいること
- 2回の run_stem と、録画ファイルの場所・解像度・長さ・大きさ・コマ数
- 上の「確かめること」の答え
- 台帳の行数と先頭 667 行の md5
- 決めてほしいこと（あれば）
```

### 報告：20261029_2102_lp_before_after_rec.txt

```
====================================================================
LP 用に、②の丸テーブルのまわりを「直す前・直した後」で録画した
依頼：20261029_26_LP用に店2の直す前と後を録画する.md
2026-10-29 21:02　check-in：cs:41（録画のしくみだけ）　最後の状態：cs:41 head・保留 0
====================================================================

結論

  ・始める前の確かめ5つは、すべて合格だった。
  ・Unity Recorder は入っていなかった。足していない（理由は 2 章）。代わりに、撮るための小さなスクリプト2つを足して cs:41 で check-in した。
    店・人・ロボットのコードと設定は変えていない。
  ・2回とも completed で、録画は 2 本ずつ（寄り・全体）。どれも 3751 コマ・125.03 秒・30 コマ/秒・H.264・CRF 16・yuv420p。
  ・直す前の回では、丸テーブルのまわりの 60 秒の「動けなくなった」が 8 回あった。p0check（画面なし）と時刻も場所も同じ。
    ロボット2 が往復4で、t=1021.2〜1527.5 秒、抜けられずに待っている。
  ・直した後の回では、同じ範囲の 60 秒の「動けなくなった」は 0。
  ・ただし、直した後の 900〜1650 秒には「片方が T05・T06 に止まっている間に、もう1台が間を抜ける」場面はなかった（4 章の 3）。
  ・ロボットの位置（5 秒ごと）は、画面なしの回とまったく同じだった（直した後は t=6290 秒まで、直す前は t=6550 秒まで）。
    録画した 900〜1650 秒の動きは、録画で変わっていない。そのあとはずれていき、回全体の数は少し違う（4 章の 5）。
  ・直す前の回のフォルダ名が直した後と同じになってしまうので、ラベルを lpdemobefore にした（決めてほしいこと 1）。
  ・台帳は 667 → 669 行（追記だけ）。先頭 667 行の md5 は始める前と同じ。


====================================================================
1. 始める前の確かめ（5つとも合格。20:40 ごろ）
====================================================================

  1. Unity エディタ：起動していない（Unity Hub だけが起動していた）
  2. /Volumes/KIOXIA：マウントされている。unity_ledger_v1.lock：なし
  3. 台帳 unity_ledger_v1.csv：667 行・md5 c7e561fec03ba49cb6cf5fa44628fe5e（依頼と同じ）
  4. ServiceSim の保留：0（cs:40 head）
  5. iCloud の unity/site_copy_dffc100b269c/ と unity/site_copy_08db8a0d067c/：どちらもある
  追加の確かめ：cs:39 と cs:40 の違いは、params・summary.json・6店の JSON と地図の 14 ファイルだけ。コードは同じ（前提どおり）


====================================================================
2. 足したものと cs 番号
====================================================================

  cs:41（4 ファイル。コメント「LP用の録画のしくみを足す…」）
    Assets/ServiceSim/Recording/SimTimeRecorder.cs（＋.meta）
      ・起動の引数に -ssRecord を付けたときだけ動く。夜の回・段階B・エディタでの Play では何もしない。シーンにも置いていない
      ・撮る時刻は店の中の時刻（stops.csv の t_s と同じ。Time.time − 回し始めの時刻）。900〜1650 秒を 0.2 秒ごとに撮る
        回は今までどおり 1コマ 0.05 秒で進むので、撮る重さで変わるのは実時間だけ（4 コマに1回撮る）
      ・真上・正投影の録画用カメラを自分で作る（ふだんは切っておき、撮るコマだけ描く）。背景色・映すものは Main Camera の設定を写す。
        画面の上の文字（OnGUI）は入らない。暗い膜・ぼかし・文字の重ねは入れていない
      ・描いた絵をそのまま ffmpeg に渡す。設定は libx264・preset slow・CRF 16・yuv420p・30 コマ/秒
      ・その回の出力フォルダの下に recordings/ を作って書く。recordings/ がもうあれば撮らない（上書きしない）
    Assets/ServiceSim/Editor/RecordBuild.cs（＋.meta）
      ・録画用の実行ファイルを、夜の実行ファイル（Builds/headless）とは別の場所に作る（中身の作り方は BuildHeadless と同じ）。
        Builds/headless（cs:40 の夜用）は触っていない

  Unity Recorder を足さなかった理由
    ・p0check・p0fixcheck は実行ファイル（player）で回した回。同じ道で回さないと比べられない
    ・Recorder はエディタの中でしか動かず、player では撮れない
    ・使い道がないので足さなかった。パッケージ（manifest.json）は変えていない

  回し方
    ・実行ファイル：-batchmode だけで、-nographics は付けない（描くため。HUD・1倍の待ちは -batchmode なので動かない）
      引数 -ssCondition fam_p0_s2_r2_a19 -ssSeed 0 -ssNight 20261029 -ssParallel 1 -ssRunning 1
      　　 -ssPassBy on -ssRuleB off -ssStandbyReturn on（今の標準）
    ・ffmpeg：/opt/homebrew/Caskroom/miniforge/base/envs/ros_env/bin/ffmpeg（7.1.1。PATH にはなかったので、このパスを渡した）

  最後の状態：cs:41 head・保留 0（cm status --machinereadable は STATUS 行だけ）


====================================================================
3. 2回の run_stem と録画ファイル
====================================================================

  置き場所：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/{run_stem}/recordings/
  2回とも、映す範囲・向き・カメラ・光・品質は同じ（同じスクリプト・同じ引数・同じシーン）

  直した後（店の形の版 08db8a0d067c・cs:41・保留 0）
    run_stem：20261029_lpdemo_cs41_fam_p0_r2_s0_passon_sbret（completed・実時間 3.5 分・build_id cs41_20261029_2050）
    20261029_2050_lpdemo_after_A_close.mp4   1680×1080・125.03 秒・3751 コマ・2,027,031 バイト
    20261029_2050_lpdemo_after_B_whole.mp4   1920×1560・125.03 秒・3751 コマ・2,673,237 バイト
    20261029_2050_lpdemo_after_A_close_frames.csv / _B_whole_frames.csv（frame, t_s。0 → 900.0 秒 … 3750 → 1650.0 秒。間はすべて 0.2 秒）

  直す前（店の形の版 dffc100b269c・cs:41 に保留 17 件：写した 14 ファイルと、建てたときに触った .asset など 3 件）
    run_stem：20261029_lpdemobefore_cs41_fam_p0_r2_s0_passon_sbret（completed・実時間 3.6 分・build_id cs41_20261029_2055）
    20261029_2055_lpdemo_before_A_close.mp4  1680×1080・125.03 秒・3751 コマ・3,307,186 バイト
    20261029_2055_lpdemo_before_B_whole.mp4  1920×1560・125.03 秒・3751 コマ・2,518,174 バイト
    20261029_2055_lpdemo_before_A_close_frames.csv / _B_whole_frames.csv（同じく 900.0〜1650.0 秒・0.2 秒ごと）

  映像A（寄り）：真上から正投影。x 3.0〜10.0・y 10.5〜15.0（7.0 × 4.5 m。1 m ＝ 240 画素）。+y が画面の上
  映像B（全体）：真上から正投影。x −0.8〜24.8・y −0.8〜20.0（店の外寸 24 × 19.2 m に 0.8 m の余白。ServiceSimCameras.FrameTop と同じ余白）
  見た目：ロボット白・スタッフ青・客黄。ロボットの道の線（薄いオレンジ）は今の標準のまま映る
  A の範囲についての気づき：T01・T04 の止まる位置は入るが、卓そのもの（T01 x 0〜2.6・T04 y 15.0〜16.2）は範囲の外で映らない

  直す前の作り方（依頼の 3）
    ・site_copy_dffc100b269c の params・summary.json・6店の JSON と地図（14 ファイル）を cp で写した。
      写したあと、14 ファイルとも cs:39 に check-in された中身と同じことを cmp で確かめた
    ・JSON Store「② を建てて確かめる」：版 dffc100b269c・卓 19・席 88・合わないもの なし・止まる位置は乗らない 0 / 19・
      受け渡し口から全 19 卓がつながった
    ・Map Planner「② 確かめ A」：引けない卓 0 / 19・待機場所3つとも引けた → 通った
    ・録画のあと cm undo で 17 件を戻した。params と②の JSON・地図が 08db8a0d067c の写しと同じに戻ったことを cmp で確かめた。保留 0
    ・JSON Store は時刻の入らない名前で unity/screens/ に書き出す（20261029_p0_memory_json_check.txt・
      20261029_p0_memory_table_dist.csv・20261029_family_v0_* など）。直す前の版で上書きされたので、戻したあとにもう一度
      「② を建てて確かめる」を回し、今の版（08db8a0d067c・合わないもの なし）の中身に書き直した。そのとき触った .asset 3 件も cm undo で戻した。
      20261029_2055_* の名前のもの（直す前の確かめ A）は残してある


====================================================================
4. 確かめたことの答え
====================================================================

  1. 直す前の録画の回の、丸テーブルのまわり（x 4.75〜6.85・y 11.3〜14.0）の 60 秒の「動けなくなった」
     8 回あった。p0check と時刻・場所・理由が同じ（1 行ずつ同じ）
       ロボット1 往復3：t=948.1・1039.85・1267.0（(5.36, 12.41)・椅子）
       ロボット2 往復4：t=1104.75（椅子）・1184.5・1265.15・1345.75・1420.95（もう1台のロボット）（(5.85, 11.98)）
     → p0check（ロボット2・往復4・t=1021〜1527 秒ごろ）と同じ時刻に起きた

  2. 直した後の録画の回の、同じ範囲の 60 秒の「動けなくなった」
     0 回。p0fixcheck と同じ
     900〜1650 秒のロボットの位置（robot_track.csv・5 秒ごと）は p0fixcheck と1行も違わない
     （ずれ始めるのは t=6290 秒。直す前も、p0check と t=6550 秒まで同じ）

  3. 直した後の回で、片方が T05 か T06 に止まっている間に、もう1台が2つの丸テーブルの間を通り抜けた時刻（900〜1650 秒）
     なかった。この間に丸テーブルへ行ったのは1台ずつで、そのとき、もう1台は待機場所にいるか受け渡し口へ戻る途中だった
       ロボット1 が T05 に止まる：t≒950〜970・1270〜1290（もう1台は待機場所）
       ロボット2 が T05 に止まる：t≒1050〜1070・1330〜1350（もう1台は待機場所か受け渡し口のそば）
     回全体で探すと、そういう場面は1つだけだった（範囲の外）
       t≒4855〜4880：ロボット2 が T05 に止まっている間に、ロボット1 が T06 へ入る（4858 秒ごろ）。
       そのあと、ロボット1 が T06 に止まっている間に、ロボット2 が丸テーブルの間を抜けて受け渡し口へ戻る（4875 秒ごろ）。
       p0fixcheck でも同じ時刻に起きている
     （robot_track.csv の 5 秒ごとの位置を線でつないで探した。秒は ±5 秒くらい）

  4. 直す前の録画で、もう1台が抜けられずに待っている場面の始まりと終わり
     ロボット2（往復4・T05 へ）：始まり t=1021.2（(6.13, 11.85) で止まる）〜 終わり t=1527.5（動き出す）。T05 に着いたのは t=1545.0
       映像のコマでは 606 〜 3137 番（mp4 の 20.2 〜 104.6 秒）
     ロボット2 の前で止まっていたのはロボット1（往復3・T03 へ行く途中）。T05 に止まっていたのではない。
       (5.36, 12.41) で t≒948 から動けず、t=1526.8 に往復が打ち切られ、受け渡し口のそばへ瞬間移動した
       （t=1525 には (5.36, 12.41)、t=1530 には (9.75, 5.44)）。この瞬間移動は映像Aにも映る（コマ 3134 前後）

  5. 録画した回と画面なしの回の差（回全体。台帳と stops.csv）
                                 直す前：録画 ／ p0check     直した後：録画 ／ p0fixcheck
     30 秒以上の止まり             15 ／ 17                    6 ／ 5
     動けなくなった（60 秒）       12 ／ 12                    5 ／ 2
     往復の数                      73 ／ 73                    74 ／ 76
     入店待ち（分）                1.42 ／ 1.45                1.56 ／ 1.22
     料理の待ち（分）              0.98 ／ 0.94                0.61 ／ 0.54
     会計待ち（分）                1.18 ／ 1.16                0.67 ／ 0.88
     打ち切り                      1 ／ 1                      0 ／ 0
     あきらめた                    13 ／ 12                    10 ／ 4
     瞬間移動                      14 ／ 15                    11 ／ 15
     店の時間（分）                163.3 ／ 164.0              165.2 ／ 162.7
     ・900〜1650 秒は2回とも位置が同じなので、録画した場面は画面なしの回と同じ動き
     ・違いが出るのは t=6290 秒（直した後）・6550 秒（直す前）より後。直した後は入店待ち +0.35 分・動けなくなった +3 で、差が大きい
       直した後の、丸テーブルの外の「動けなくなった」5 回の場所：待機場所1のそば (12.24, 5.51) 1 回・
       入口とレジのまわり（(12.8〜13.5, 1.7〜3.3)。スタッフ・お客さん）4 回
     ・ずれた理由は調べていない。今までの記録（busycheck）でも、同時に回す本数で数が動くと分かっている。
       画面ありで回した分だけ1コマの実時間が変わったことが関わっている、と思うが、確かめていない


====================================================================
5. 台帳
====================================================================

  行数：667 → 669（2 行足した。追記だけ。列は 59 列のまま）
    20261029_lpdemo_cs41_fam_p0_r2_s0_passon_sbret       completed・ServiceSim/cs:41・08db8a0d067c
    20261029_lpdemobefore_cs41_fam_p0_r2_s0_passon_sbret completed・ServiceSim/cs:41・dffc100b269c
  先頭 667 行の md5：c7e561fec03ba49cb6cf5fa44628fe5e（始める前と同じ）
  鍵ファイルは残っていない
  ※ 直す前の行の unity_cs は「cs:41」だが、実際は cs:41 のコードに、保留として cs:39 と同じデータを入れたもの。
    見分けは layout_generation（dffc100b269c）でつく
  make_queue.py・night_runner.py・judge_night.py・p2m-site は変えていない


====================================================================
6. 残ったもの（消そうとしたが、消す許可が出なかった）
====================================================================

  録画のしくみの試し（店の時刻 20〜26 秒だけ撮り、回の途中で止めた。台帳には書いていない）の出力が KIOXIA に残っている
    /Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261029_rectest_cs40_fam_p0_r2_s0_passon_sbret/（recordings/ に試しの mp4 2 本）
    /Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261029_rectest_cs40_fam_p0_r2_s0_passon_sbret_stopped/（途中で止めた回の記録）
  プロジェクトの Builds/rec_test・rec_after・rec_before（Builds/ は VCS の外）


====================================================================
7. 決めてほしいこと
====================================================================

  1. 直す前の回のフォルダ名。
     同じ夜・同じラベル lpdemo・同じ cs41 だと、直した後と1字も違わない名前になる（直す前の状態は check-in しないため）。
     -ssTry 2 で分けると、judge_night.py が「やり直し」として1つにまとめてしまう。そのため、ラベルを lpdemobefore にして回した。
     このままでよいか。名前を変えるなら、フォルダと台帳の run_folder をどうそろえるか。
  2. 直した後の「すれ違って抜ける」場面が、録画した 900〜1650 秒の中にない。
     回全体で1つだけあり、t≒4855〜4880 秒（画面なしの回でも同じ時刻）。
     見せたいなら、直した後をこの時刻のあたり（例 4800〜4950 秒）で撮り足すかどうか。
     依頼で撮り直してよいのは「直す前に詰まりが出なかったとき」だけなので、撮り直していない。
  3. 録画した回の数が、画面なしの回と t≒6300 秒より後でずれる（直した後は、入店待ち 1.56 ／ 1.22 分・動けなくなった 5 ／ 2）。
     録画の場面（900〜1650 秒）は同じ動きだが、LP に数字を並べるなら、画面なしの回（p0check・p0fixcheck）の数を使うのがよいと思う。どちらを使うか。
  4. 試しの出力（6 章の rectest 2 フォルダ）を消すか残すか。
```

### 依頼箱の結果：20261029_26_LP用に店2の直す前と後を録画する_20261029_2039.md

```markdown
# 結果：20261029_26_LP用に店2の直す前と後を録画する

- 状態：完了
- 始めた時刻：2026-10-29 20:39
- 終わった時刻：2026-10-29 21:04（25分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 685755d2-3a74-45bf-82aa-e5af669ecbb8`
- 止められた操作：1件（いちばん下に一覧）

## Claude Code の報告

**やったこと**
- 始める前の確かめ5つは、すべて合っていました（エディタは起動していない／鍵ファイルなし／台帳 667 行・md5 一致／cs:40 head で保留 0／site_copy は2つともある）。cs:39 と cs:40 の違いはデータの 14 ファイルだけで、コードは同じでした。
- Unity Recorder は入っていませんでしたが、足していません。p0check と同じく実行ファイルで回すので、エディタの中でしか動かない Recorder は使えないためです。
- 代わりに、撮るための小さなスクリプト2つを足し、cs:41 で check-in しました。
  - 1つは、起動の引数に -ssRecord を付けたときだけ動き、店の時刻に合わせて真上のカメラで撮るものです。
  - もう1つは、録画用の実行ファイルを夜用とは別の場所に作るものです。
  - 店・人・ロボットのコードと設定は変えていません。
- 直した後は、cs:41 のまま録画しました。
- 直す前は、dffc の 14 ファイルを写しました（cs:39 の中身と1バイトも違わないことを確かめた）。そのあと「② を建てて確かめる」と「② 確かめ A」を回し（どちらも合格）、録画しました。終わってから `cm undo` で戻し、cs:41 head・保留 0 を確かめました。

**結果**
- **録画**：4本とも 3751 コマ・125.03 秒・30 コマ/秒・H.264・CRF 16・yuv420p です。撮った時刻は 900.0〜1650.0 秒で、0.2 秒ごとに抜けなく撮れています。コマと時刻の対応表（CSV）も一緒に置きました。
  - 直した後：`20261029_lpdemo_cs41_fam_p0_r2_s0_passon_sbret/recordings/`
    - 寄り：1680×1080、2.0 MB
    - 全体：1920×1560、2.7 MB
  - 直す前：`20261029_lpdemobefore_cs41_fam_p0_r2_s0_passon_sbret/recordings/`
    - 寄り：1680×1080、3.3 MB
    - 全体：1920×1560、2.5 MB
- **直す前の詰まり**：丸テーブルのまわりの 60 秒の「動けなくなった」は8回ありました。p0check と時刻・場所・理由が1行ずつ同じです。
  - ロボット2 は t=1021.2〜1527.5 秒、抜けられずに待っています（映像のコマで 606〜3137 番）。
  - 前にいたロボット1 は、T05 に止まっていたのではありません。T03 へ行く途中で t≒948 秒から動けず、t=1526.8 秒に往復が打ち切られて瞬間移動しました。この瞬間移動も映像に映ります。
- **直した後**：同じ範囲の「動けなくなった」は 0 回です。
- **見せたい場面**：直した後の 900〜1650 秒には、「片方が T05・T06 に止まっている間に、もう1台が間を抜ける」場面はありませんでした。回全体では t≒4855〜4880 秒に1つだけあり、画面なしの回でも同じ時刻に起きています。
- **録画で動きが変わったか**：900〜1650 秒のロボットの位置（5 秒ごと）は、画面なしの回と1行も違いません。録画した場面の動きは変わっていません。
- **回全体の数のずれ**：直した後は t=6290 秒から、直す前は t=6550 秒から、画面なしの回とずれ始めます。
  - 直す前は、画面なしの回とほぼ同じです（30 秒以上の止まり 15／17、往復 73／73、入店待ち 1.42／1.45 分）。
  - 直した後は差が大きく出ました（動けなくなった 5／2、往復 74／76、入店待ち 1.56／1.22 分）。ずれた理由は調べていません。
- **台帳**：667 → 669 行（追記だけ、59 列のまま）。先頭 667 行の md5 は c7e561fec03ba49cb6cf5fa44628fe5e で、始める前と同じです。

**変えたファイル**
- 足して check-in したもの（cs:41）：`Assets/ServiceSim/Recording/SimTimeRecorder.cs`、`Assets/ServiceSim/Editor/RecordBuild.cs`（どちらも .meta つき）
- 報告：`claude_code_reports/20261029_2102_lp_before_after_rec.txt`
- screens の書き出し：時刻の入らない名前の 20261029 の書き出しが、直す前の版で上書きされました。戻したあとにもう一度建てて、今の版の中身に書き直してあります。
- 消せずに残ったもの：録画のしくみを試した回のフォルダ2つ（`20261029_rectest_cs40_…` と、その `_stopped`）です。消す許可が出ませんでした。台帳には書いていません。

**決めてほしいこと**
1. **直す前の回のフォルダ名**：同じ夜・同じラベル・同じ cs41 だと、直した後と同じ名前になります。`-ssTry 2` で分けると judge_night.py が「やり直し」として1つにまとめてしまうので、ラベルを `lpdemobefore` にしました。このままでよいか。
2. **見せたい場面を撮り足すか**：「すれ違って抜ける」場面は録画した時間の中にありません。t≒4800〜4950 秒あたりで直した後を撮り足すかどうか。依頼の範囲では撮り直していません。
3. **LP に載せる数字**：録画した回は、回全体の数が画面なしの回と少し違います。LP に数字を並べるなら、画面なしの回（p0check・p0fixcheck）の数を使うのがよいと思います。どちらを使うか。
4. **試した回の出力**：rectest のフォルダ2つを消すか残すか。

## 止められた操作

- {"tool_name": "Bash", "tool_use_id": "toolu_014pgGzjdZcPDLiPJz7sqrfn", "tool_input": {"command": "cd /Users/shimazueiichi/Robotics_UX/ServiceSim; U=/Volumes/KIOXIA/r2r2r-data/service_sim/unity; rm -rf $U/20261029_rectest_cs40_fam_p0_r2_s0_passon_sbret $U/20261029_rectest_cs40_fam_p0_r2_s0_passon_sbret_stopped; ls $U | grep -c rectest; rm -rf Builds/rec_test; wc -l < $U/unity_ledger_v1.csv; cm log cs:40 --csformat=\"{comment}\" 2>&1 | head -10; cm status --short", "description": "Clean test outpu
```

### Unity Recorder は使えず、店の時刻に合わせて撮る部品を足した

依頼では Unity Recorder（Unity の画面を動画にする部品）を想定していましたが、画面なしで回す作りでは、エディタの中でしか動かない Recorder は使えませんでした。Claude Code は代わりに、起動の引数に `-ssRecord` を付けたときだけ動く部品（SimTimeRecorder.cs）と、録画用の実行ファイルを別の場所に作る部品（RecordBuild.cs）を足しました。店の時刻0.2秒ごとに1コマを撮るので、Mac が忙しくても撮れる場面は変わりません。2つの部品の全文と、版ごとの差分は、影を撮り直した節のあとに載せます。

直す前の店は、cs:39 と同じデータ14ファイル（店の形の版 dffc100b269c）を写して建て直し、撮ったあと元に戻しています。cs:39 と cs:40 はコードが同じで、違うのは店の形と条件のデータだけだったためです。

### 録画した場面の動きは、画面なしで回した回と同じだった

| 比べたもの | 直す前 | 直した後 |
|---|---|---|
| 丸テーブルのまわりの60秒以上の止まり | 8回（画面なしの回と、時刻・場所・理由が1行ずつ同じ） | 0回 |
| ロボットの位置（900〜1650秒・5秒ごと） | 画面なしの回と違う行 0 | 画面なしの回と違う行 0 |
| 回全体でずれ始めた時刻 | 6,550秒から | 6,290秒から |

録画した900〜1650秒の範囲は、画面なしの回とまったく同じ動きでした。回の後半でずれ始める理由は、録画の範囲の外なので調べていません。LP に載せる数字（8→0回）は、画面なしで回した回（p0check・p0fixcheck）の数を使います。

もう1つ分かったのは、直した後の900〜1650秒には、「片方のロボットが卓に止まっている横を、もう1台が抜ける」場面がなかったことです。その場面は回の後半（経過81分ごろ）に1つだけあります。撮り足すかを考えましたが、同じ時刻で比べられることのほうが説得力があるので、同じ時刻のままにしました。


## 作業ログ ― 確認用のページで、並べる形と切り替える形を見比べた

### 録画の中身を、コマの一覧で確かめた

録画（125秒・3,751コマ）のどこに何が映っているかを、150コマおきに並べた一覧と、75コマおきの帯で確かめました。

```bash
#!/bin/bash
# 録画（寄り・125秒・3751コマ）のどこに何が映っているかを、コマの一覧で確かめる（LOG404）
S=/tmp/claude-0/-home-claude/32de2f9c-f50b-5d31-bb77-ea18dc2b20df/scratchpad/lp
cd $S/raw
# 150コマ（店の時刻30秒）おきに25枚を、5×5の1枚に並べる
ffmpeg -v error -y -i *after_A_close.mp4 -vf "select=not(mod(n\,150)),scale=336:-1,tile=5x5" -frames:v 1 $S/fr/after_sheet.jpg
ffmpeg -v error -y -i *before_A_close.mp4 -vf "select=not(mod(n\,150)),scale=336:-1,tile=5x5" -frames:v 1 $S/fr/before_sheet.jpg
# 150〜1050コマ目を75コマおきに13枚、横1列に並べ、直す前と直した後を上下に重ねる（左上の数字は何枚目か。コマは 150+75×その数）
for v in before after; do ffmpeg -v error -y -i *${v}_A_close.mp4 -vf "select=between(n\,150\,1050)*not(mod(n-150\,75)),scale=280:-1,drawtext=text='%{n}':x=5:y=5:fontsize=20:fontcolor=red,tile=13x1" -frames:v 1 $S/fr/${v}_strip.jpg; done
ffmpeg -v error -y -i $S/fr/before_strip.jpg -i $S/fr/after_strip.jpg -filter_complex vstack $S/fr/strips.jpg
# 決めたい時刻のコマを1枚ずつ取り出して、左右に並べる（1500コマ目＝店の時刻1200秒）
for v in before after; do f=$(ls *${v}_A_close.mp4); for n in 0 606 1500 3000 3137 3300; do ffmpeg -v error -y -i $f -vf "select=eq(n\,$n)" -frames:v 1 $S/fr/${v}_$n.jpg; done; done
ffmpeg -v error -y -i $S/fr/before_1500.jpg -i $S/fr/after_1500.jpg -filter_complex "[0]scale=840:-1[a];[1]scale=840:-1[b];[a][b]hstack" $S/fr/pair_1500.jpg
```

直す前は、606コマ目（店の時刻1021秒）からロボット2台が丸テーブルの間で並んで止まり、青いスタッフがまわりを行き来します。直した後の同じ時刻は、ロボットが間を抜けて先へ進み、丸テーブルのまわりは空いています。直す前の1527秒ごろには、往復が打ち切られたロボット1が瞬間移動する（動けなくなった回を先へ進めるための、シミュレーションの逃がし弁）ので、その手前で切ることにしました。

### 経過15:30〜18:00を、12倍速・約12秒のループに切り出した cut_loops.sh

切り出しには [ffmpeg](https://ffmpeg.org/)（動画を切ったり縮めたりする、コマンドで動かす道具）を使いました。録画の150〜900コマ目（店の時刻930〜1080秒）を2倍にして12倍速にし、右のパントリーの壁から先を切り落として、右下に店の時刻を入れました。

```bash
#!/bin/bash
# LP（配膳ロボット）の「どこで止まるか」に並べる、②の丸テーブルの直す前・直した後のループを、Unity の寄りの録画（1680×1080・0.2秒ごとに1コマ・30コマ/秒＝6倍速）から切り出す（LOG404）
# 使ったのは録画の 150〜900 コマ目＝店の時刻 930〜1080 秒（経過 15:30〜18:00）。さらに2倍にして 12倍速・約12.5秒にする
# 右のパントリーの壁から先は要らないので、左から 1240px を切り取り、992×864 に縮める。右下に店の時刻（分:秒）を入れる
# 1回目（依頼26・光 52°）は箱の色を 0x121418、2回目（依頼28・光 75°・LP に載せた版）は LP の文字の色 0x242424 にした
set -e
RAW="$1"   # 録画を写したフォルダ（*_before_A_close.mp4 と *_after_A_close.mp4 がある）
OUT="$2"   # 書き出す先
mkdir -p "$OUT"
FONT=/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf
for v in before after; do
  ffmpeg -v error -y -i "$RAW"/*${v}_A_close.mp4 \
    -vf "trim=start_frame=150:end_frame=901,setpts=(PTS-STARTPTS)/2,fps=30,crop=1240:1080:0:0,scale=992:864:flags=lanczos,drawtext=fontfile=$FONT:text='%{eif\:(930+(n*0.4))/60\:d\:2}\:%{eif\:mod(930+n*0.4\,60)\:d\:2}':x=w-tw-20:y=h-th-18:fontsize=30:fontcolor=white:box=1:boxcolor=0x242424@0.75:boxborderw=10" \
    -c:v libx264 -preset veryfast -crf 22 -pix_fmt yuv420p -movflags +faststart -an "$OUT/round-${v}.mp4"
  # 動画が読み込まれる前に出す1コマ目（poster）
  ffmpeg -v error -y -i "$OUT/round-${v}.mp4" -frames:v 1 -c:v libwebp -quality 80 "$OUT/round-${v}.webp"
done
ffprobe -v error -show_entries format=duration -of csv=p=0 "$OUT/round-before.mp4"
ls -la "$OUT"
```

### cut_loops.sh の出力

```
12.533333
total 292
drwxr-xr-x 2 root root   4096 Oct 29 22:57 .
drwxr-xr-x 4 root root   4096 Oct 29 22:57 ..
-rw-r--r-- 1 root root 124815 Oct 29 22:57 round-after.mp4
-rw-r--r-- 1 root root  12882 Oct 29 22:57 round-after.webp
-rw-r--r-- 1 root root 127990 Oct 29 22:57 round-before.mp4
-rw-r--r-- 1 root root  12796 Oct 29 22:57 round-before.webp
```

1本は約12.5秒で、大きさは約0.12MBでした。同じスクリプトを録画から回し直すと、LP に置いたファイルと1バイトも違わないものができます（cmp で確かめた）。

### 確認用のページで、並べる形（A）と切り替える形（B）を出した

LP に入れる前に、確認用のページ（サイトには載せない）を作りました。A は左右に並べて同時に流す形、B は1つの枠で「直す前」「直した後」をボタンで切り替える形です。見て決めてほしいところとして、見せ方・影・右の場面・長さの4つを並べました。

> 横並びで伝わるから切り替えボタンは必要ないね

影については、「消すのは変なので短くする」と決まりました。

> 影：影を短くする（全くないのは変）

右の場面は、同じ時刻に通り抜けるままにしました。

### 確認用のページ（影を撮り直したあとの版）

最初の版にあった B の節と「見て決めてほしいところ」の節は、決まったあとに外しました。外した2つの節は、このページの下に続けて載せます。

```html
<title>丸テーブル前後比較</title>
<meta name="claude:viewport-fit-sides" content="cover">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700;900&family=IBM+Plex+Mono:wght@500;600&display=swap">
<style>
/* LP「どこで止まるか」に入れる前の確認用。LP と同じ明るいグレーの面・黒の文字・橙は1か所だけ */
:root{
  --bg:#f4f5f7; --panel:#ffffff; --fg:#121418; --sub:#5c616b; --line:#d9dce1; --accent:#f2600c; --dark:#121418; --on-dark:#ffffff;
  --sans:"Noto Sans JP","Hiragino Sans",system-ui,sans-serif; --mono:"IBM Plex Mono",ui-monospace,monospace;
}
@media (prefers-color-scheme: dark){:root:not([data-theme="light"]){--bg:#15171b;--panel:#1d2026;--fg:#eceef1;--sub:#a3a8b2;--line:#30343c;--dark:#eceef1;--on-dark:#121418;color-scheme:dark}}
:root[data-theme="dark"]{--bg:#15171b;--panel:#1d2026;--fg:#eceef1;--sub:#a3a8b2;--line:#30343c;--dark:#eceef1;--on-dark:#121418;color-scheme:dark}
body{background:var(--bg);color:var(--fg);font:400 15px/1.8 var(--sans);word-break:auto-phrase}
.wrap{max-width:1040px;margin:0 auto;padding-block:40px 64px;padding-left:max(16px,env(safe-area-inset-left,0px));padding-right:max(16px,env(safe-area-inset-right,0px));display:grid;gap:48px}
.label{font:700 12px/1 var(--sans);letter-spacing:.08em;color:var(--sub)}
h1{margin:8px 0 0;font:900 26px/1.45 var(--sans);text-wrap:balance}
h2{margin:0;font:900 20px/1.5 var(--sans);text-wrap:balance}
p{margin:0}
.lead{color:var(--sub);max-width:42em}
.sec{display:grid;gap:16px}
.pair{display:grid;grid-template-columns:1fr 1fr;gap:16px}
@media (max-width:640px){.pair{grid-template-columns:1fr}}
figure{margin:0;display:grid;gap:10px;min-width:0}
video{display:block;width:100%;max-width:100%;height:auto;aspect-ratio:992/864;background:#e8e2d6;border-radius:4px}
.cap{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.tag{display:inline-block;padding:4px 10px;border-radius:4px;font:700 13px/1.4 var(--sans);background:var(--dark);color:var(--on-dark)}
.tag.after{background:var(--accent);color:#fff}
.cap span{font-size:14px;color:var(--sub)}
.num{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;padding:16px 20px;background:var(--panel);border-radius:6px}
.num b{font:600 34px/1 var(--mono)}
.num em{font:600 34px/1 var(--mono);font-style:normal;color:var(--accent)}
.num i{font-style:normal;color:var(--sub)}
.num small{font-size:14px;color:var(--sub)}
.num span{flex-basis:100%;font-size:14px}
.note{font-size:13px;color:var(--sub)}
.switch{display:grid;gap:12px;max-width:560px}
.seg{display:inline-flex;gap:4px;padding:4px;background:var(--panel);border-radius:6px;justify-self:start}
.seg button{font:700 14px/1 var(--sans);padding:10px 18px;border:0;border-radius:4px;background:transparent;color:var(--fg);cursor:pointer}
.seg button[aria-pressed="true"]{background:var(--dark);color:var(--on-dark)}
.seg button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.stack{position:relative}
.stack video{position:relative}
.stack video+video{position:absolute;inset:0}
.checks{display:grid;gap:10px;padding:20px;background:var(--panel);border-radius:6px}
.checks h2{font-size:17px}
.checks ul{margin:0;padding-left:1.2em;display:grid;gap:6px}
</style>

<div class="wrap">
  <header>
    <p class="label">確認用（サイトには載せていません）</p>
    <h1>丸テーブルの間を広げると、ロボットは止まらなくなった</h1>
    <p class="lead">記憶の店（② 定番の郊外型）を3Dで建て、同じ客・同じ注文・同じ時刻で2回走らせました。違うのは、右の丸テーブルの位置だけです。ロボット2台・真上から・12倍速。右下の数字は、シミュレーションを始めてからの経過時間（分:秒）です。</p>
  </header>

  <section class="sec" aria-labelledby="a-h">
    <p class="label">影を短くして撮り直した版（光の高さ 52°→75°）</p>
    <div class="pair" id="pair">
      <figure>
        <video src="srl-before.mp4" poster="srl-before.jpg" muted playsinline preload="auto" aria-label="直す前。丸テーブルの間に白いロボット2台が止まり、青いスタッフがまわりを回る"></video>
        <figcaption class="cap"><b class="tag">直す前</b><span>丸テーブルの間 1.1m</span></figcaption>
      </figure>
      <figure>
        <video src="srl-after.mp4" poster="srl-after.jpg" muted playsinline preload="auto" aria-label="直した後。同じ時刻に、ロボットは丸テーブルの間を通り抜けていく"></video>
        <figcaption class="cap"><b class="tag after">直した後</b><span>右の丸テーブルを1.0m右へ／間 2.1m</span></figcaption>
      </figure>
    </div>
    <div class="num">
      <b>8</b><i>→</i><em>0</em><small>回</small>
      <span>丸テーブルの間で、ロボットが60秒以上動けなくなった回数（ロボット2台・1回分）</span>
    </div>
  </section>


</div>

<script>
(function(){
  function sync(vs){
    var go=function(){vs.forEach(function(v){try{v.currentTime=0;var p=v.play();if(p&&p.catch)p.catch(function(){});}catch(e){}});};
    vs.forEach(function(v){v.addEventListener('ended',go);});
    var n=0;vs.forEach(function(v){v.addEventListener('canplay',function once(){v.removeEventListener('canplay',once);if(++n===vs.length)go();});});
  }
  var pair=[].slice.call(document.querySelectorAll('#pair video'));
  var st=[].slice.call(document.querySelectorAll('#stack video'));
  var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  if(!reduce){sync(pair);sync(st);}else{pair.concat(st).forEach(function(v){v.controls=true;});}
  var b=document.getElementById('swBefore'),a=document.getElementById('swAfter');
  function show(after){st[0].hidden=after;st[1].hidden=!after;b.setAttribute('aria-pressed',String(!after));a.setAttribute('aria-pressed',String(after));}
  b.addEventListener('click',function(){show(false);});
  a.addEventListener('click',function(){show(true);});
})();
</script>
```

### 確認用のページの最初の版にあった、切り替える形（B）と確かめの節

```html
  <section class="sec" aria-labelledby="b-h">
    <p class="label">見せ方B：1つの枠で切り替える</p>
    <div class="switch">
      <div class="seg" role="group" aria-label="直す前と直した後を切り替える">
        <button type="button" id="swBefore" aria-pressed="true">直す前</button>
        <button type="button" id="swAfter" aria-pressed="false">直した後</button>
      </div>
      <div class="stack" id="stack">
        <video src="srl-before.mp4" poster="srl-before.jpg" muted playsinline preload="auto" aria-label="直す前"></video>
        <video src="srl-after.mp4" poster="srl-after.jpg" muted playsinline preload="auto" aria-label="直した後" hidden></video>
      </div>
      <p class="note">切り替えても時刻はそろったまま流れます。</p>
    </div>
  </section>

  <section class="checks">
    <h2>見て決めてほしいところ</h2>
    <ul>
      <li>見せ方：A（並べる）か B（切り替える）か</li>
      <li>影：斜めの長い影が、客や椅子より目立っています。録画のときだけ影を消す（光を真上にする）こともできます。シミュレーションの動きは変わりません</li>
      <li>右の場面：同じ時刻なので、直した後は「通り抜けて、空いている」が見えます。もう1台が卓に止まっている横を抜ける場面は、この回の後半（経過81分ごろ）に1つだけあり、撮り足すこともできます</li>
      <li>長さ：経過15:30〜18:00の2分半を12倍速にした約12秒のループです。直す前の2台目は17:00ごろに止まり、そこから約8分動けません。ループは止まって1分のところで終わります</li>
    </ul>
  </section>
```


## 作業ログ ― 光を52°から75°に上げて、影を短くして撮り直した（依頼28）

最初の録画では、店を照らす光（影を落とす Directional Light）の地面からの角度が52°で、客や椅子の影が斜めに長く伸び、ロボットより目立っていました。録画のときだけ光を高くするよう頼みました。比べるために、直した後の同じコマ（店の時刻1500秒）を70°・75°・80°で書き出してもらいました。

【画像】LOG404_02_sun_angle_52_70_75_80.png 直した後の②の同じコマ（店の時刻1500秒）を、光の角度52°（最初の録画）・70°・75°・80°で並べた図。52°は客と椅子の影が斜めに長く伸び、75°では足元に少し出るくらい、80°ではほぼ真下になる

75°で撮り直しました。影は足元に少し残り、立体に見えます。光が真上に近づいたぶん、床が明るく見えますが、明るさの設定は変えていません。

- ねらい：影より、ロボットとスタッフと客の動きが目に入るようにする
- 変わったこと：光の角度 52° → 75°（録画のときだけ。夜に回すシミュレーションの見た目と動きは変えない）
- 見込める効果：スマホの小さな画面でも、止まっている白いロボット2台がすぐ見分けられる。ロボットの位置は、52°の録画と900〜1650秒で1行も違わなかった（測った値）

### 依頼文：LP用の録画を影を短くして撮り直す（20261029_28）

```markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 90
---
# 依頼：依頼26の寄りの録画（映像A）を、影を短くして直す前・直した後とも撮り直す（2026-10-29）

依頼26 の報告（20261029_2102_lp_before_after_rec.txt）の「決めてほしいこと」への答え（エイイチと決めた）：
- 1：直す前のラベル lpdemobefore はこのままでよい
- 2：見せたい場面の撮り足しはしない。同じ時刻（900〜1650 秒）で比べる
- 3：LP の数字は画面なしの回（p0check・p0fixcheck）の 8 → 0 を使う
- 4：rectest のフォルダ2つは残す（触らない）

確認用に並べて見たところ、斜めの長い影が客や椅子より目立っていた。影は消さずに短くする（まったくないのは変）。

## やること

1. 録画のときだけ（-ssRecord のときだけ）、店を照らす光（Directional Light）の高さを上げて影を短くする。目安は地面からの角度 75°（今の向き＝方角は変えない）。影のやわらかさ・濃さ・光の色と明るさは今のまま。録画しない実行（夜の回・確かめの回）の見た目と動きは何も変えない。変えた分は check-in する
2. 撮る前に、直した後の②で、t=1500 秒あたりの1コマを 70°・75°・80° の3通りで静止画に書き出し（寄りの範囲と同じ 1680×1080）、報告に場所を書く。録画は 75° で進める
3. 依頼26 と同じやり方・同じ条件で撮り直す（② × ロボット2台 × 種0・今の標準・900〜1650 秒・0.2 秒ごとに1コマ・30 コマ/秒・H.264 CRF 16・yuv420p・コマと時刻の CSV つき）
   - 撮るのは映像A（寄り：x 3.0〜10.0・y 10.5〜15.0・1680×1080）だけ。映像B（全体）は要らない
   - ラベル：直した後 `lpdemo2`、直す前 `lpdemo2before`
   - 直す前は依頼26 と同じく site_copy_dffc100b269c の14ファイルを写して②を建て直し、撮ったら cm undo で戻して保留 0 を確かめる
4. 依頼26 の録画と、900〜1650 秒のロボットの位置（5 秒ごと）が1行も違わないことを確かめる（光を変えても動きが変わっていないことの確かめ）

## 始める前に確かめること（違えば手を止めて報告）

1. Unity エディタが起動していない
2. /Volumes/KIOXIA がマウントされている。鍵ファイル unity_ledger_v1.lock がない
3. 台帳が 669 行
4. ServiceSim の保留が 0（cs:41 head）

## 変えないもの

- 店の回し方・人とロボットの動き・速さ・Stiffness(20,000)/Damping(2,000) などの物理の設定
- 録画しない実行の光と見た目
- 依頼26 の録画と出力（上書きしない。同じ名前のフォルダがあれば止める）
- 台帳の列と今までの行（追記だけ。669 → 671 行。先頭 669 行の md5 を始める前と比べる）
- サイト（p2m-site）は触らない。git clean・git add . は使わない

## 報告

依頼箱の結果に加えて、claude_code_reports に `20261029_HHMM_lp_rec_short_shadow.txt` を書く。
- 始める前の確かめの結果
- 光をどう変えたか（角度・どこで切り替えているか）と cs 番号、最後に保留 0
- 3通りの角度の静止画の場所
- 2本の録画の場所・解像度・長さ・大きさ・コマ数
- 依頼26 の録画とロボットの位置が同じだったか
- 台帳の行数と先頭 669 行の md5
- 決めてほしいこと（あれば）
```

### 報告：20261029_2228_lp_rec_short_shadow.txt

```
====================================================================
LP 用の寄りの録画（映像A）を、影を短くして直す前・直した後とも撮り直した
依頼：20261029_28_LP用の録画を影を短くして撮り直す.md
2026-10-29 22:28　check-in：cs:42・cs:43（録画のしくみだけ）　最後の状態：cs:43 head・保留 0
====================================================================

結論

  ・始める前の確かめ4つは、すべて合格だった。
  ・録画のとき（-ssRecord のとき）だけ、影を落とす光 Sun の地面からの角度を 52° → 75° にした。方角（326°＝−34°）・
    やわらかさ・濃さ・色・明るさは変えていない。録画しない回では何も変わらない。
  ・2本とも completed。どちらも 1680×1080・125.03 秒・3751 コマ・30 コマ/秒・H.264・CRF 16・yuv420p。
  ・900〜1650 秒のロボットの位置（5 秒ごと・302 行）は、依頼26 の録画と1行も違わなかった（直す前・直した後とも）。
  ・まちがいが1つある。静止画を書き出す最初の回が、撮れないまま最後まで回り、台帳に1行余計に書いてしまった。
    台帳は 669 → 672 行（予定の 671 より 1 行多い。追記だけ。先頭 669 行の md5 は始める前と同じ）。決めてほしいこと 1。


====================================================================
1. 始める前の確かめ（4つとも合格。22:02 ごろ）
====================================================================

  1. Unity エディタ：起動していない（Unity Hub だけ）
  2. /Volumes/KIOXIA：マウントされている。unity_ledger_v1.lock：なし
  3. 台帳 unity_ledger_v1.csv：669 行・md5 261326722474707bd8637f948a5183f7
  4. ServiceSim の保留：0（cs:41 head）
  同じ名前のフォルダ（20261029_lpdemo2_* など）：なし


====================================================================
2. 光をどう変えたかと cs 番号
====================================================================

  変えたのは Assets/ServiceSim/Recording/SimTimeRecorder.cs だけ（-ssRecord を付けた回だけ動く部品）。
  シーン・Setup（ServiceSimSetup.cs の Sun＝Euler(52, −34, 0)）・夜の実行ファイル Builds/headless は変えていない。

  切り替える場所
    ・SimTimeRecorder.Begin()（録画の最初のコマを撮る直前。店の時刻 900 秒）で、影を落とす Directional Light
      （シーンの Lighting/Sun。影ありの Directional Light がちょうど1つのときだけ）の回転を Euler(75, 326, 0) にする。
      y（方角）と z はもとの値のまま、x（地面からの角度）だけを変える
    ・Fill（影なしの弱い光）・環境光・影の設定・色・明るさは触らない
    ・-ssRecSunPitch で角度を変えられる（既定 75）
    ・ログの記録：「光の角度: Sun 52° → 75°（方角 326° のまま）」
  静止画だけを書く回（-ssRecStillAt 1500 -ssRecStillPitches 70,75,80）も足した。指定した時刻のコマを、角度ごとに PNG で書き、
    回を途中で終える（台帳には書かない。記録は {run_stem}_stopped に残る）

  cs:42　光の切り替えと静止画の回を足す
  cs:43　cs:42 の静止画の回の誤りを直す（3 章）。始まりと終わりの時刻を1コマ分あける・撮れないときは回さずに終える
  最後の状態：cs:43 head・保留 0（cm status --machinereadable は STATUS 行だけ）


====================================================================
3. 3通りの角度の静止画（直した後の②・店の時刻 t=1500.0 秒・1680×1080）
====================================================================

  /Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261029_lpdemo2still_cs43_fam_p0_r2_s0_passon_sbret/recordings/
    20261029_2219_lpdemo2_after_still_A_close_t1500_sun70.png（288,516 バイト）
    20261029_2219_lpdemo2_after_still_A_close_t1500_sun75.png（266,563 バイト）
    20261029_2219_lpdemo2_after_still_A_close_t1500_sun80.png（244,645 バイト）
  見た感じ：70° でも依頼26（52°）よりずっと短い。75° は客・椅子の影が足元に少し出るくらい。80° はほぼ真下。録画は 75° で進めた。

  まちがい（cs:42 の回）
    cs:42 では、静止画の回の始まりと終わりを同じ時刻（1500 秒）にしていた。録画のしくみの最初の確かめで「撮れない」と判断され、
    撮らないまま最後まで回った。そのため台帳に 1 行書かれた（20261029_lpdemo2still_cs42_fam_p0_r2_s0_passon_sbret・completed）。
    行は消していない。直したあと cs:43 で撮り直した（台帳には書かれなかった）。


====================================================================
4. 2本の録画（映像A だけ。x 3.0〜10.0・y 10.5〜15.0）
====================================================================

  回し方は依頼26 と同じ（-batchmode のみ・fam_p0_s2_r2_a19・種 0・すれ違い 入・案B 切・待機場所へ戻る 入・同時 1）。
  録画の引数も同じ（900〜1650 秒・0.2 秒ごと・30 コマ/秒・CRF 16）。違うのは光の角度だけ（-ssRecSunPitch 75）

  直した後（店の形の版 08db8a0d067c・cs:43・保留 0・build cs43_20261029_2219）
    /Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261029_lpdemo2_cs43_fam_p0_r2_s0_passon_sbret/recordings/
      20261029_2220_lpdemo2_after_A_close.mp4　1680×1080・125.03 秒・3751 コマ・1,755,197 バイト
      20261029_2220_lpdemo2_after_A_close_frames.csv（0 → 900.0 秒 … 3750 → 1650.0 秒。間はすべて 0.2 秒）
    completed・実時間 2.3 分

  直す前（店の形の版 dffc100b269c・cs:43 に保留 17 件・build cs43_20261029_2224）
    /Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261029_lpdemo2before_cs43_fam_p0_r2_s0_passon_sbret/recordings/
      20261029_2224_lpdemo2_before_A_close.mp4　1680×1080・125.03 秒・3751 コマ・2,702,127 バイト
      20261029_2224_lpdemo2_before_A_close_frames.csv（同じく 900.0〜1650.0 秒・0.2 秒ごと）
    completed・実時間 2.6 分
  どちらも h264・yuv420p・30/1・crf=16.0（ffprobe と mp4 の中の設定で確かめた）
  コマと時刻の CSV は、依頼26 のものと1字も違わない（cmp）

  直す前の作り方（依頼26 と同じ）
    ・site_copy_dffc100b269c の 14 ファイルを cp で写した。14 とも cs:39 の中身と同じことを cmp で確かめた
    ・JSON Store CheckP0：版 dffc100b269c・合わないもの なし・止まる位置は乗らない 0 / 19・全 19 卓がつながった
    ・Map Planner CheckP0：引けない卓 0 / 19 → 確かめ A は通った
    ・撮ったあと cm undo で 17 件を戻した。14 ファイルが 08db8a0d067c の写しと同じに戻ったことを cmp で確かめた。保留 0
    ・JSON Store が時刻のない名前で unity/screens/ に書くファイル 9 つは、建て直す前に控えをとっておき、戻したあと控えから書き戻した
      （9 つとも控えと同じ。20261029_p0_memory_json_check.txt は版 08db8a0d067c・合わないもの なし）
      20261029_2224_map_plan_p0_memory.csv・20261029_2224_map_planner_checkA_p0.txt（直す前の確かめ A）は残してある


====================================================================
5. 依頼26 の録画とロボットの位置
====================================================================

  robot_track.csv の 900〜1650 秒（5 秒ごと・302 行）
    直した後：lpdemo（cs41）と lpdemo2（cs43）　違う行 0
    直す前　：lpdemobefore（cs41）と lpdemo2before（cs43）　違う行 0
  → 光を変えても、録画した場面の動きは変わっていない。
  回全体では、直した後は t=6290 秒から（依頼26 で p0fixcheck とずれ始めた時刻と同じ）、
  直す前は t=3385 秒からずれる（録画の範囲の外。依頼26 の報告 4 章の 5 と同じく、理由は調べていない）。
  並べて見たコマ（mp4 の 60 秒目）でも、ロボット・スタッフ・客の位置は同じで、影だけが短くなっていた。


====================================================================
6. 台帳
====================================================================

  行数：669 → 672（3 行足した。追記だけ）
    20261029_lpdemo2still_cs42_fam_p0_r2_s0_passon_sbret   completed・cs:42・08db8a0d067c　← 余計な行（3 章のまちがい）
    20261029_lpdemo2_cs43_fam_p0_r2_s0_passon_sbret        completed・cs:43・08db8a0d067c
    20261029_lpdemo2before_cs43_fam_p0_r2_s0_passon_sbret  completed・cs:43・dffc100b269c
  先頭 669 行の md5：261326722474707bd8637f948a5183f7（始める前と同じ）
  鍵ファイルは残っていない
  ※ 直す前の行の unity_cs は「cs:43」だが、実際は cs:43 のコードに、保留として cs:39 と同じデータを入れたもの（依頼26 と同じ）。


====================================================================
7. 残ったもの（消していない）
====================================================================

  KIOXIA
    20261029_lpdemo2still_cs42_fam_p0_r2_s0_passon_sbret/（撮れずに最後まで回った回の記録。recordings/ は空）
    20261029_lpdemo2still_cs43_fam_p0_r2_s0_passon_sbret/（静止画 3 枚）
    20261029_lpdemo2still_cs43_fam_p0_r2_s0_passon_sbret_stopped/（途中で終えた回の記録）
  プロジェクトの Builds/rec2_after（cs:42）・rec2_after43・rec2_before43・rec2_compiletest（Builds/ は VCS の外）
  依頼26 の録画・出力、rectest の 2 フォルダは触っていない。p2m-site・make_queue.py・night_runner.py・judge_night.py は変えていない。


====================================================================
8. 決めてほしいこと
====================================================================

  1. 台帳の余計な 1 行（20261029_lpdemo2still_cs42_…・completed）をどうするか。
     静止画を書き出す回のつもりが、撮れないまま最後まで回った回。台帳は追記だけの決まりなので消していない。
     中身は「直した後の②・cs:42・録画なし」のふつうの 1 回分（入店待ち 1.69 分）。このまま残すか、印を付けるなどするか。
  2. 光の角度を上げたので、床などに光が真上近くから当たるようになり、画面全体が少し明るく見える（明るさの設定は変えていない）。
     依頼26 の録画と並べると色合いが少し違う。このままでよいか。
```

### 依頼箱の結果：20261029_28_LP用の録画を影を短くして撮り直す_20261029_2202.md

```markdown
# 結果：20261029_28_LP用の録画を影を短くして撮り直す

- 状態：完了
- 始めた時刻：2026-10-29 22:02
- 終わった時刻：2026-10-29 22:28（26分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 5a3ac635-5f19-4f05-865f-416886b4b7cc`
- 止められた操作：1件（いちばん下に一覧）

## Claude Code の報告

**やったこと**
- 始める前の確かめ4つは、すべて合格でした。Unity エディタは起動しておらず、KIOXIA はマウントされ、鍵ファイルはありませんでした。台帳は 669 行、ServiceSim の保留は 0（cs:41）でした。
- 録画のとき（-ssRecord のとき）だけ、影を落とす光（Sun）の高さを 52° から 75° に上げるようにし、check-in しました（cs:42・cs:43）。方角（326°）・影のやわらかさ・濃さ・光の色・明るさは変えていません。切り替えは、録画の最初のコマを撮る直前に行います。録画しない回は何も変わりません。
- 直した後の②で、t=1500 秒のコマを 70°・75°・80° の3通りで静止画に書き出しました。
- 75° で、映像A だけを撮り直しました（直した後 `lpdemo2`、直す前 `lpdemo2before`）。
- 直す前は依頼26 と同じく、14 ファイルを写して②を建て直しました。撮ったあとは cm undo で戻し、保留が 0 になったことを確かめました。建て直しで上書きされた unity/screens/ の9ファイルは、先に控えを取っておき、そこから書き戻しました。
- **まちがいが1つあります。** 静止画を書き出す最初の回（cs:42）で、始まりと終わりの時刻を同じにしていました。そのため撮れないまま最後まで回り、台帳に余計な1行を書いてしまいました。cs:43 で直して撮り直しました。

**結果**
- 静止画（1680×1080）の置き場所は `/Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261029_lpdemo2still_cs43_fam_p0_r2_s0_passon_sbret/recordings/` です。ファイル名の最後が `_sun70.png`・`_sun75.png`・`_sun80.png` の3枚です。
- 直した後の録画：`20261029_lpdemo2_cs43_fam_p0_r2_s0_passon_sbret/recordings/20261029_2220_lpdemo2_after_A_close.mp4`
  - 1680×1080・125.03 秒・3751 コマ・1,755,197 バイト
- 直す前の録画：`20261029_lpdemo2before_cs43_fam_p0_r2_s0_passon_sbret/recordings/20261029_2224_lpdemo2_before_A_close.mp4`
  - 1680×1080・125.03 秒・3751 コマ・2,702,127 バイト
- 2本とも、H.264・CRF 16・yuv420p・30 コマ/秒です。コマと時刻の CSV は、依頼26 のものと1字も違いません。
- 900〜1650 秒のロボットの位置（5 秒ごと・302 行）は、直す前・直した後とも依頼26 と違う行が 0 でした。光を変えても動きは変わっていません。
- 台帳は 669 → 672 行で、予定より1行多くなりました。追記だけで、先頭 669 行の md5 は 261326722474707bd8637f948a5183f7 のまま、始める前と同じです。
- 最後の状態は cs:43 head・保留 0 です。

**変えたファイル**
- `Assets/ServiceSim/Recording/SimTimeRecorder.cs`（cs:42・cs:43）
- 報告：`claude_code_reports/20261029_2228_lp_rec_short_shadow.txt`

**決めてほしいこと**
1. 台帳の余計な1行（`20261029_lpdemo2still_cs42_fam_p0_r2_s0_passon_sbret`・completed）をどうするか。台帳は追記だけの決まりなので、消していません。中身は、直した後の②を cs:42 で録画なしに1回回したものです。このまま残すか、印を付けるかを決めてください。
2. 光が真上に近くなったので、明るさの設定は同じでも、画面全体が少し明るく見えます。依頼26 の録画と並べると色合いが少し違いますが、このままでよいかを決めてください。

## 止められた操作

- {"tool_name": "Bash", "tool_use_id": "toolu_01MDVqauCr1qDEvD6oYCM3SY", "tool_input": {"command": "cd /Users/shimazueiichi/Robotics_UX/ServiceSim; cm status --machinereadable; rm -rf Builds/rec2_compiletest", "description": "Check pending changes, remove test build"}}
```

台帳に1行余計に入った件（静止画の回の始まりと終わりを同じ時刻にした間違いで、撮れないまま最後まで回った1回）は、消さずに残すことにしました。台帳は追記だけの決まりで、ラベルが `lpdemo2still` なので、夜の回の判定には混ざりません。明るさの変化も、このままにしました。

### 録画の部品 SimTimeRecorder.cs（cs:43・全文）

```csharp
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using UnityEngine;
using Debug = UnityEngine.Debug;

namespace ServiceSim
{
    /// <summary>
    /// 見せるための録画（2026-10-29・依頼 26「LP用に店2の直す前と後を録画する」）。
    ///
    /// 引数に -ssRecord が付いたときだけ立つ。夜の回・段階B・エディタの Play では何もしない。
    /// シーンには置かない（起動したあとに自分で GameObject を1つ作る）。
    ///
    /// 撮り方
    ///   ・店の中の時刻（stops.csv の t_s と同じ。Time.time − 回し始めの時刻）が
    ///     from 〜 to の間、step 秒ごとに1コマ撮る。
    ///     回は1コマ 0.05 秒固定（Time.captureDeltaTime）で進むので、
    ///     撮る重さで店の中の動きは変わらない（実時間が延びるだけ）。
    ///   ・自分で作った真上のカメラ（正投影・ふだんは切）を、撮るコマだけ
    ///     RenderTexture に描いて読み、ffmpeg に生のまま渡して mp4 にする。
    ///     画面の上の文字（OnGUI）は RenderTexture に入らない。
    ///   ・店のもの・人・ロボット・ほかのカメラには触らない。
    ///     見た目は Main Camera の設定（背景色・映すもの）を写して使う。
    ///   ・光：影を落とす Directional Light（Setup の Sun）の高さだけを上げて影を短くする
    ///     （2026-10-29・依頼 28。地面からの角度 52° → 75°。方角・やわらかさ・濃さ・色・明るさは今のまま）。
    ///     撮り始めるときに変える。-ssRecord の無い回（夜の回・確かめの回・エディタの Play）では変わらない。
    ///
    /// 引数
    ///   -ssRecord {名前の頭}     … mp4 の名前の頭（例 20261029_1930_lpdemo_after）
    ///   -ssRecFrom 900 -ssRecTo 1650 -ssRecStep 0.2
    ///   -ssRecFps 30 -ssRecCrf 16
    ///   -ssRecFfmpeg {ffmpeg のパス}
    ///   -ssRecView name,x0,x1,y0,y1,w,h   … 撮る範囲（店の座標 m）と大きさ。何本でも
    ///   -ssRecSunPitch 75                 … 録画のときの光の地面からの角度（既定 75。依頼 28）
    ///   -ssRecStillAt 1500 -ssRecStillPitches 70,75,80
    ///                                     … 静止画だけ書く回（依頼 28）。店の時刻がこの秒に来たコマを、
    ///                                       光の角度ごとに PNG で書いて、回を途中で終える（mp4 は作らない。
    ///                                       台帳には書かれず、記録は {run_stem}_stopped に残る）
    ///
    /// 置き場所：その回の出力フォルダ（HeadlessRun が決めた run_stem）の下の recordings/。
    ///   recordings/ がもうあれば撮らない（上書きしない）。
    /// </summary>
    public class SimTimeRecorder : MonoBehaviour
    {
        const float CamHeight = 60f;
        /// <summary>録画のときの光の地面からの角度（依頼 28。ふだんの Sun は 52°）</summary>
        const float DefaultSunPitch = 75f;

        class View
        {
            public string name;
            public float x0, x1, y0, y1;
            public int w, h;
            public Camera cam;
            public RenderTexture rt;
            public Texture2D tex;
            public Process proc;
            public Stream stdin;
            public string mp4;
            public string err = "";
            public StringBuilder ffLog = new StringBuilder();
        }

        string _prefix = "", _ffmpeg = "ffmpeg";
        float _from = 900f, _to = 1650f, _step = 0.2f;
        int _fps = 30, _crf = 16;
        float _sunPitch = DefaultSunPitch;
        float _stillAt = -1f;                       // 0 以上なら静止画だけ書く回
        readonly List<float> _stillPitches = new List<float>();
        Light _sun;
        readonly List<View> _views = new List<View>();

        ExperimentRunner _runner;
        HeadlessRun _headless;
        FieldInfo _fOffset, _fJobRunning, _fRunDir;

        bool _started, _done, _failed;
        int _next;                // 次に撮るコマの番号（0 から）
        string _dir = "";
        StringWriter _csv;
        int _frames;

        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
        static void Boot()
        {
            string[] argv;
            try { argv = Environment.GetCommandLineArgs(); }
            catch (Exception) { return; }
            if (Find(argv, "-ssRecord") == null) return;

            GameObject go = new GameObject("SimTimeRecorder");
            SimTimeRecorder r = go.AddComponent<SimTimeRecorder>();
            r.Setup(argv);
        }

        void Setup(string[] argv)
        {
            _prefix = Find(argv, "-ssRecord") ?? "";
            _ffmpeg = Find(argv, "-ssRecFfmpeg") ?? "ffmpeg";
            _from = F(Find(argv, "-ssRecFrom"), _from);
            _to = F(Find(argv, "-ssRecTo"), _to);
            _step = F(Find(argv, "-ssRecStep"), _step);
            _fps = (int)F(Find(argv, "-ssRecFps"), _fps);
            _crf = (int)F(Find(argv, "-ssRecCrf"), _crf);
            _sunPitch = F(Find(argv, "-ssRecSunPitch"), _sunPitch);
            _stillAt = F(Find(argv, "-ssRecStillAt"), -1f);
            string pitches = Find(argv, "-ssRecStillPitches");
            if (pitches != null)
                foreach (string p in pitches.Split(',')) _stillPitches.Add(F(p, _sunPitch));
            if (_stillAt >= 0f)
            {
                if (_stillPitches.Count == 0) _stillPitches.Add(_sunPitch);
                _from = _stillAt; _to = _stillAt + _step;   // 1コマだけ撮る（_to <= _from の確かめに掛からないように）
            }

            for (int i = 0; i < argv.Length - 1; i++)
            {
                if (argv[i] != "-ssRecView") continue;
                string[] p = argv[i + 1].Split(',');
                if (p.Length != 7) { Log("-ssRecView が読めません: " + argv[i + 1]); continue; }
                View v = new View();
                v.name = p[0];
                v.x0 = F(p[1], 0); v.x1 = F(p[2], 0); v.y0 = F(p[3], 0); v.y1 = F(p[4], 0);
                v.w = (int)F(p[5], 0); v.h = (int)F(p[6], 0);
                _views.Add(v);
            }

            _fOffset = typeof(ExperimentRunner).GetField("_timeOffset", BindingFlags.Instance | BindingFlags.NonPublic);
            _fJobRunning = typeof(ExperimentRunner).GetField("_jobRunning", BindingFlags.Instance | BindingFlags.NonPublic);
            _fRunDir = typeof(HeadlessRun).GetField("_runDir", BindingFlags.Instance | BindingFlags.NonPublic);

            if (_prefix.Length == 0 || _views.Count == 0 || _step <= 0f || _to <= _from ||
                _fOffset == null || _fJobRunning == null || _fRunDir == null)
            {
                Log("撮れません（引数か、ExperimentRunner / HeadlessRun の中の名前が合いません）。この回は撮らずに回します");
                FailSetup();
                return;
            }
            if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Null)
            {
                Log("描画の装置がありません（-nographics では撮れません）。この回は撮らずに回します");
                FailSetup();
                return;
            }
            if (_stillAt >= 0f)
            {
                Log("静止画だけ書きます: 店の時刻 " + _stillAt + " 秒・光の角度 " + string.Join(",", _stillPitches) +
                    "°・" + _views.Count + " 本。書いたら回を途中で終えます");
                return;
            }
            Log("撮ります: 店の時刻 " + _from + "〜" + _to + " 秒・" + _step + " 秒ごと・" + _fps + " コマ/秒・CRF " + _crf +
                "・" + _views.Count + " 本");
        }

        void LateUpdate()
        {
            if (_failed || _done) return;

            if (_runner == null) _runner = FindObjectOfType<ExperimentRunner>();
            if (_headless == null) _headless = FindObjectOfType<HeadlessRun>();
            if (_runner == null || _headless == null) return;
            if (!(bool)_fJobRunning.GetValue(_runner)) return;

            float t = Time.time - (float)_fOffset.GetValue(_runner);
            float target = _from + _next * _step;
            if (target > _to + 1e-3f) { Close(); return; }
            if (t < target - 1e-3f) return;

            if (!_started && !Begin()) { _failed = true; QuitIfStill(); return; }

            if (_stillAt >= 0f) { WriteStills(t); return; }

            for (int i = 0; i < _views.Count; i++) Grab(_views[i]);
            _csv.Write(_frames.ToString(CultureInfo.InvariantCulture));
            _csv.Write(',');
            _csv.Write(t.ToString("0.###", CultureInfo.InvariantCulture));
            _csv.Write('\n');
            _frames++;
            _next++;
            // 1コマ 0.05 秒で進むので、ふつうは1つずつ。万一飛んでも、次の目標へ進める
            while (_from + _next * _step <= t + 1e-3f) _next++;
        }

        bool Begin()
        {
            _started = true;
            string runDir = _fRunDir.GetValue(_headless) as string;
            // 出力フォルダは回の終わりに ExperimentRunner が作る（Directory.CreateDirectory なので、
            // 先にあっても困らない）。無いことは HeadlessRun が回し始める前に確かめてある
            if (string.IsNullOrEmpty(runDir))
            {
                Log("出力フォルダの名前が分かりません。撮りません");
                return false;
            }
            _dir = Path.Combine(runDir, "recordings");
            if (Directory.Exists(_dir))
            {
                Log("recordings/ がもうあります。上書きしないので撮りません: " + _dir);
                return false;
            }
            Directory.CreateDirectory(_dir);

            _sun = FindSun();
            if (_sun == null) Log("影を落とす Directional Light が1つに決まりません。光は変えずに撮ります");
            else SetSunPitch(_sunPitch);

            Camera main = Camera.main;
            for (int i = 0; i < _views.Count; i++)
            {
                View v = _views[i];
                GameObject go = new GameObject("RecCam_" + v.name);
                go.transform.SetParent(transform, false);
                Camera c = go.AddComponent<Camera>();
                if (main != null) c.CopyFrom(main);
                c.enabled = false;
                c.orthographic = true;
                c.orthographicSize = (v.y1 - v.y0) * 0.5f;
                c.aspect = (float)v.w / v.h;
                c.nearClipPlane = 0.1f;
                c.farClipPlane = CamHeight + 20f;
                c.transform.position = new Vector3((v.x0 + v.x1) * 0.5f, CamHeight, (v.y0 + v.y1) * 0.5f);
                c.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
                v.rt = new RenderTexture(v.w, v.h, 24, RenderTextureFormat.ARGB32);
                v.rt.antiAliasing = 8;
                v.rt.Create();
                c.targetTexture = v.rt;
                v.cam = c;
                v.tex = new Texture2D(v.w, v.h, TextureFormat.RGB24, false);
                if (_stillAt >= 0f) continue;   // 静止画だけの回は ffmpeg を立てない

                v.mp4 = Path.Combine(_dir, _prefix + "_" + v.name + ".mp4");
                string args = "-hide_banner -loglevel error -y -f rawvideo -pix_fmt rgb24 -s " + v.w + "x" + v.h +
                              " -r " + _fps + " -i - -vf vflip -c:v libx264 -preset slow -crf " + _crf +
                              " -pix_fmt yuv420p -movflags +faststart \"" + v.mp4 + "\"";
                ProcessStartInfo psi = new ProcessStartInfo(_ffmpeg, args);
                psi.UseShellExecute = false;
                psi.RedirectStandardInput = true;
                psi.RedirectStandardError = true;
                psi.CreateNoWindow = true;
                v.proc = new Process();
                v.proc.StartInfo = psi;
                View vv = v;
                v.proc.ErrorDataReceived += (s, e) => { if (e.Data != null) lock (vv.ffLog) vv.ffLog.AppendLine(e.Data); };
                v.proc.Start();
                v.proc.BeginErrorReadLine();
                v.stdin = v.proc.StandardInput.BaseStream;
            }

            _csv = new StringWriter(CultureInfo.InvariantCulture);
            _csv.Write("frame,t_s\n");
            Log("撮り始めました: " + _dir);
            return true;
        }

        void FailSetup()
        {
            _failed = true;
            QuitIfStill();
        }

        /// <summary>静止画だけの回は、撮れないなら最後まで回さずに終える（台帳に行を残さない）</summary>
        void QuitIfStill()
        {
            if (_stillAt < 0f) return;
            Log("静止画だけの回なので、撮れないまま回さずに終えます");
            Application.Quit(1);
        }

        /// <summary>影を落とす Directional Light（Setup の Sun）。1つに決まらなければ null</summary>
        static Light FindSun()
        {
            Light found = null;
            foreach (Light l in FindObjectsOfType<Light>())
            {
                if (l.type != LightType.Directional || l.shadows == LightShadows.None) continue;
                if (found != null) return null;
                found = l;
            }
            return found;
        }

        /// <summary>地面からの角度だけを変える（方角＝y の回転・z の回転はそのまま）</summary>
        void SetSunPitch(float pitch)
        {
            if (_sun == null) return;
            Vector3 e = _sun.transform.eulerAngles;
            float before = e.x;
            _sun.transform.rotation = Quaternion.Euler(pitch, e.y, e.z);
            Log("光の角度: " + _sun.name + " " + before.ToString("0.#", CultureInfo.InvariantCulture) + "° → " +
                pitch.ToString("0.#", CultureInfo.InvariantCulture) + "°（方角 " +
                e.y.ToString("0.#", CultureInfo.InvariantCulture) + "° のまま）");
        }

        /// <summary>静止画だけの回：光の角度ごとに PNG を書いて、回を途中で終える</summary>
        void WriteStills(float t)
        {
            _done = true;
            StringBuilder note = new StringBuilder();
            for (int k = 0; k < _stillPitches.Count; k++)
            {
                float pitch = _stillPitches[k];
                SetSunPitch(pitch);
                for (int i = 0; i < _views.Count; i++)
                {
                    View v = _views[i];
                    v.cam.Render();
                    RenderTexture prev = RenderTexture.active;
                    RenderTexture.active = v.rt;
                    v.tex.ReadPixels(new Rect(0, 0, v.w, v.h), 0, 0, false);
                    v.tex.Apply(false);
                    RenderTexture.active = prev;
                    string png = Path.Combine(_dir, _prefix + "_" + v.name + "_t" +
                                              t.ToString("0.##", CultureInfo.InvariantCulture) + "_sun" +
                                              pitch.ToString("0.#", CultureInfo.InvariantCulture) + ".png");
                    File.WriteAllBytes(png, v.tex.EncodeToPNG());
                    note.Append("\n  ").Append(png);
                }
            }
            for (int i = 0; i < _views.Count; i++) if (_views[i].rt != null) _views[i].rt.Release();
            Log("静止画を書きました（店の時刻 " + t.ToString("0.###", CultureInfo.InvariantCulture) + " 秒）" + note +
                "\n  回を途中で終えます（台帳には書きません）");
            Application.Quit(0);
        }

        void Grab(View v)
        {
            if (v.stdin == null) return;
            v.cam.Render();
            RenderTexture prev = RenderTexture.active;
            RenderTexture.active = v.rt;
            v.tex.ReadPixels(new Rect(0, 0, v.w, v.h), 0, 0, false);
            RenderTexture.active = prev;
            byte[] raw = v.tex.GetRawTextureData();
            try { v.stdin.Write(raw, 0, raw.Length); }
            catch (Exception e) { v.err = e.Message; v.stdin = null; }
        }

        void Close()
        {
            if (_done) return;
            _done = true;
            if (!_started) return;

            for (int i = 0; i < _views.Count; i++)
            {
                View v = _views[i];
                try { if (v.stdin != null) { v.stdin.Flush(); v.stdin.Close(); } } catch (Exception e) { v.err = e.Message; }
            }
            StringBuilder note = new StringBuilder();
            for (int i = 0; i < _views.Count; i++)
            {
                View v = _views[i];
                try { if (v.proc != null) v.proc.WaitForExit(); } catch (Exception) { }
                int code = -1;
                try { code = v.proc.ExitCode; } catch (Exception) { }
                File.WriteAllText(Path.Combine(_dir, _prefix + "_" + v.name + "_frames.csv"), _csv.ToString(),
                                  new UTF8Encoding(false));
                note.Append("\n  ").Append(v.mp4).Append("（").Append(v.w).Append('×').Append(v.h)
                    .Append("・ffmpeg 終了コード ").Append(code)
                    .Append(v.err.Length > 0 ? "・" + v.err : "")
                    .Append(v.ffLog.Length > 0 ? "・" + v.ffLog.ToString().Trim() : "").Append('）');
                if (v.rt != null) v.rt.Release();
            }
            Log("撮り終わりました: " + _frames + " コマ" + note);
        }

        void OnApplicationQuit() { Close(); }
        void OnDestroy() { Close(); }

        static string Find(string[] argv, string name)
        {
            for (int i = 0; i < argv.Length - 1; i++)
                if (argv[i] == name) return argv[i + 1];
            return null;
        }

        static float F(string s, float def)
        {
            float v;
            return s != null && float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out v) ? v : def;
        }

        static void Log(string msg) { Debug.Log("[ServiceSim/rec] " + msg); }
    }
}
```

### 録画用の実行ファイルを作る RecordBuild.cs（cs:43・全文）

```csharp
using System;
using UnityEditor;
using UnityEngine;

namespace ServiceSim.EditorTools
{
    /// <summary>
    /// 録画に使う実行ファイルを、夜の実行ファイル（Builds/headless）とは別の場所に作る
    /// （2026-10-29・依頼 26。中身は BuildHeadless と同じ。置き場所だけ違う）。
    ///
    /// 夜の実行係が使う Builds/headless を、録画のための版で上書きしないため。
    /// 回すときは -nographics を付けない（-batchmode だけ）。付けると描けないので撮れない。
    ///
    ///   Unity -batchmode -quit -projectPath … \
    ///     -executeMethod ServiceSim.EditorTools.RecordBuild.Build -ssBuildOut Builds/rec_after
    /// </summary>
    public static class RecordBuild
    {
        public static void Build()
        {
            string outDir = Find(Environment.GetCommandLineArgs(), "-ssBuildOut");
            if (string.IsNullOrEmpty(outDir) || outDir.TrimEnd('/') == BuildHeadless.OutDir)
            {
                Debug.LogError("[ServiceSim] -ssBuildOut に Builds/headless 以外の置き場所を入れてください");
                EditorApplication.Exit(1);
                return;
            }
            string err;
            if (!BuildHeadless.BuildTo(outDir, out err))
            {
                Debug.LogError("[ServiceSim] 録画用の実行ファイルを作れませんでした\n  " + err);
                EditorApplication.Exit(1);
                return;
            }
            EditorApplication.Exit(0);
        }

        static string Find(string[] argv, string name)
        {
            for (int i = 0; i < argv.Length - 1; i++)
                if (argv[i] == name) return argv[i + 1];
            return null;
        }
    }
}
```

### 録画の部品を足したときの差分：cs:40 → cs:41

```diff
# cm diff cs:40 cs:41 （ServiceSim）
# 1. cm diff の出力そのまま

A "Assets/ServiceSim/Recording/SimTimeRecorder.cs"
A "Assets/ServiceSim/Recording/SimTimeRecorder.cs.meta"
A "Assets/ServiceSim/Editor/RecordBuild.cs"
A "Assets/ServiceSim/Editor/RecordBuild.cs.meta"

# 2. 中身の違い（補足。上の各ファイルを cm cat <path>#cs:N で取り出し、diff -u で比べたもの）

--- /dev/null (cs:40 には無い)
+++ Assets/ServiceSim/Recording/SimTimeRecorder.cs#cs:41
@@ -0,0 +1,283 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using UnityEngine;
+using Debug = UnityEngine.Debug;
+
+namespace ServiceSim
+{
+    /// <summary>
+    /// 見せるための録画（2026-10-29・依頼 26「LP用に店2の直す前と後を録画する」）。
+    ///
+    /// 引数に -ssRecord が付いたときだけ立つ。夜の回・段階B・エディタの Play では何もしない。
+    /// シーンには置かない（起動したあとに自分で GameObject を1つ作る）。
+    ///
+    /// 撮り方
+    ///   ・店の中の時刻（stops.csv の t_s と同じ。Time.time − 回し始めの時刻）が
+    ///     from 〜 to の間、step 秒ごとに1コマ撮る。
+    ///     回は1コマ 0.05 秒固定（Time.captureDeltaTime）で進むので、
+    ///     撮る重さで店の中の動きは変わらない（実時間が延びるだけ）。
+    ///   ・自分で作った真上のカメラ（正投影・ふだんは切）を、撮るコマだけ
+    ///     RenderTexture に描いて読み、ffmpeg に生のまま渡して mp4 にする。
+    ///     画面の上の文字（OnGUI）は RenderTexture に入らない。
+    ///   ・店のもの・人・ロボット・光・ほかのカメラには触らない。
+    ///     見た目は Main Camera の設定（背景色・映すもの）を写して使う。
+    ///
+    /// 引数
+    ///   -ssRecord {名前の頭}     … mp4 の名前の頭（例 20261029_1930_lpdemo_after）
+    ///   -ssRecFrom 900 -ssRecTo 1650 -ssRecStep 0.2
+    ///   -ssRecFps 30 -ssRecCrf 16
+    ///   -ssRecFfmpeg {ffmpeg のパス}
+    ///   -ssRecView name,x0,x1,y0,y1,w,h   … 撮る範囲（店の座標 m）と大きさ。何本でも
+    ///
+    /// 置き場所：その回の出力フォルダ（HeadlessRun が決めた run_stem）の下の recordings/。
+    ///   recordings/ がもうあれば撮らない（上書きしない）。
+    /// </summary>
+    public class SimTimeRecorder : MonoBehaviour
+    {
+        const float CamHeight = 60f;
+
+        class View
+        {
+            public string name;
+            public float x0, x1, y0, y1;
+            public int w, h;
+            public Camera cam;
+            public RenderTexture rt;
+            public Texture2D tex;
+            public Process proc;
+            public Stream stdin;
+            public string mp4;
+            public string err = "";
+            public StringBuilder ffLog = new StringBuilder();
+        }
+
+        string _prefix = "", _ffmpeg = "ffmpeg";
+        float _from = 900f, _to = 1650f, _step = 0.2f;
+        int _fps = 30, _crf = 16;
+        readonly List<View> _views = new List<View>();
+
+        ExperimentRunner _runner;
+        HeadlessRun _headless;
+        FieldInfo _fOffset, _fJobRunning, _fRunDir;
+
+        bool _started, _done, _failed;
+        int _next;                // 次に撮るコマの番号（0 から）
+        string _dir = "";
+        StringWriter _csv;
+        int _frames;
+
+        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
+        static void Boot()
+        {
+            string[] argv;
+            try { argv = Environment.GetCommandLineArgs(); }
+            catch (Exception) { return; }
+            if (Find(argv, "-ssRecord") == null) return;
+
+            GameObject go = new GameObject("SimTimeRecorder");
+            SimTimeRecorder r = go.AddComponent<SimTimeRecorder>();
+            r.Setup(argv);
+        }
+
+        void Setup(string[] argv)
+        {
+            _prefix = Find(argv, "-ssRecord") ?? "";
+            _ffmpeg = Find(argv, "-ssRecFfmpeg") ?? "ffmpeg";
+            _from = F(Find(argv, "-ssRecFrom"), _from);
+            _to = F(Find(argv, "-ssRecTo"), _to);
+            _step = F(Find(argv, "-ssRecStep"), _step);
+            _fps = (int)F(Find(argv, "-ssRecFps"), _fps);
+            _crf = (int)F(Find(argv, "-ssRecCrf"), _crf);
+
+            for (int i = 0; i < argv.Length - 1; i++)
+            {
+                if (argv[i] != "-ssRecView") continue;
+                string[] p = argv[i + 1].Split(',');
+                if (p.Length != 7) { Log("-ssRecView が読めません: " + argv[i + 1]); continue; }
+                View v = new View();
+                v.name = p[0];
+                v.x0 = F(p[1], 0); v.x1 = F(p[2], 0); v.y0 = F(p[3], 0); v.y1 = F(p[4], 0);
+                v.w = (int)F(p[5], 0); v.h = (int)F(p[6], 0);
+                _views.Add(v);
+            }
+
+            _fOffset = typeof(ExperimentRunner).GetField("_timeOffset", BindingFlags.Instance | BindingFlags.NonPublic);
+            _fJobRunning = typeof(ExperimentRunner).GetField("_jobRunning", BindingFlags.Instance | BindingFlags.NonPublic);
+            _fRunDir = typeof(HeadlessRun).GetField("_runDir", BindingFlags.Instance | BindingFlags.NonPublic);
+
+            if (_prefix.Length == 0 || _views.Count == 0 || _step <= 0f || _to <= _from ||
+                _fOffset == null || _fJobRunning == null || _fRunDir == null)
+            {
+                Log("撮れません（引数か、ExperimentRunner / HeadlessRun の中の名前が合いません）。この回は撮らずに回します");
+                _failed = true;
+                return;
+            }
+            if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Null)
+            {
+                Log("描画の装置がありません（-nographics では撮れません）。この回は撮らずに回します");
+                _failed = true;
+                return;
+            }
+            Log("撮ります: 店の時刻 " + _from + "〜" + _to + " 秒・" + _step + " 秒ごと・" + _fps + " コマ/秒・CRF " + _crf +
+                "・" + _views.Count + " 本");
+        }
+
+        void LateUpdate()
+        {
+            if (_failed || _done) return;
+
+            if (_runner == null) _runner = FindObjectOfType<ExperimentRunner>();
+            if (_headless == null) _headless = FindObjectOfType<HeadlessRun>();
+            if (_runner == null || _headless == null) return;
+            if (!(bool)_fJobRunning.GetValue(_runner)) return;
+
+            float t = Time.time - (float)_fOffset.GetValue(_runner);
+            float target = _from + _next * _step;
+            if (target > _to + 1e-3f) { Close(); return; }
+            if (t < target - 1e-3f) return;
+
+            if (!_started && !Begin()) { _failed = true; return; }
+
+            for (int i = 0; i < _views.Count; i++) Grab(_views[i]);
+            _csv.Write(_frames.ToString(CultureInfo.InvariantCulture));
+            _csv.Write(',');
+            _csv.Write(t.ToString("0.###", CultureInfo.InvariantCulture));
+            _csv.Write('\n');
+            _frames++;
+            _next++;
+            // 1コマ 0.05 秒で進むので、ふつうは1つずつ。万一飛んでも、次の目標へ進める
+            while (_from + _next * _step <= t + 1e-3f) _next++;
+        }
+
+        bool Begin()
+        {
+            _started = true;
+            string runDir = _fRunDir.GetValue(_headless) as string;
+            // 出力フォルダは回の終わりに ExperimentRunner が作る（Directory.CreateDirectory なので、
+            // 先にあっても困らない）。無いことは HeadlessRun が回し始める前に確かめてある
+            if (string.IsNullOrEmpty(runDir))
+            {
+                Log("出力フォルダの名前が分かりません。撮りません");
+                return false;
+            }
+            _dir = Path.Combine(runDir, "recordings");
+            if (Directory.Exists(_dir))
+            {
+                Log("recordings/ がもうあります。上書きしないので撮りません: " + _dir);
+                return false;
+            }
+            Directory.CreateDirectory(_dir);
+
+            Camera main = Camera.main;
+            for (int i = 0; i < _views.Count; i++)
+            {
+                View v = _views[i];
+                GameObject go = new GameObject("RecCam_" + v.name);
+                go.transform.SetParent(transform, false);
+                Camera c = go.AddComponent<Camera>();
+                if (main != null) c.CopyFrom(main);
+                c.enabled = false;
+                c.orthographic = true;
+                c.orthographicSize = (v.y1 - v.y0) * 0.5f;
+                c.aspect = (float)v.w / v.h;
+                c.nearClipPlane = 0.1f;
+                c.farClipPlane = CamHeight + 20f;
+                c.transform.position = new Vector3((v.x0 + v.x1) * 0.5f, CamHeight, (v.y0 + v.y1) * 0.5f);
+                c.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
+                v.rt = new RenderTexture(v.w, v.h, 24, RenderTextureFormat.ARGB32);
+                v.rt.antiAliasing = 8;
+                v.rt.Create();
+                c.targetTexture = v.rt;
+                v.cam = c;
+                v.tex = new Texture2D(v.w, v.h, TextureFormat.RGB24, false);
+
+                v.mp4 = Path.Combine(_dir, _prefix + "_" + v.name + ".mp4");
+                string args = "-hide_banner -loglevel error -y -f rawvideo -pix_fmt rgb24 -s " + v.w + "x" + v.h +
+                              " -r " + _fps + " -i - -vf vflip -c:v libx264 -preset slow -crf " + _crf +
+                              " -pix_fmt yuv420p -movflags +faststart \"" + v.mp4 + "\"";
+                ProcessStartInfo psi = new ProcessStartInfo(_ffmpeg, args);
+                psi.UseShellExecute = false;
+                psi.RedirectStandardInput = true;
+                psi.RedirectStandardError = true;
+                psi.CreateNoWindow = true;
+                v.proc = new Process();
+                v.proc.StartInfo = psi;
+                View vv = v;
+                v.proc.ErrorDataReceived += (s, e) => { if (e.Data != null) lock (vv.ffLog) vv.ffLog.AppendLine(e.Data); };
+                v.proc.Start();
+                v.proc.BeginErrorReadLine();
+                v.stdin = v.proc.StandardInput.BaseStream;
+            }
+
+            _csv = new StringWriter(CultureInfo.InvariantCulture);
+            _csv.Write("frame,t_s\n");
+            Log("撮り始めました: " + _dir);
+            return true;
+        }
+
+        void Grab(View v)
+        {
+            if (v.stdin == null) return;
+            v.cam.Render();
+            RenderTexture prev = RenderTexture.active;
+            RenderTexture.active = v.rt;
+            v.tex.ReadPixels(new Rect(0, 0, v.w, v.h), 0, 0, false);
+            RenderTexture.active = prev;
+            byte[] raw = v.tex.GetRawTextureData();
+            try { v.stdin.Write(raw, 0, raw.Length); }
+            catch (Exception e) { v.err = e.Message; v.stdin = null; }
+        }
+
+        void Close()
+        {
+            if (_done) return;
+            _done = true;
+            if (!_started) return;
+
+            for (int i = 0; i < _views.Count; i++)
+            {
+                View v = _views[i];
+                try { if (v.stdin != null) { v.stdin.Flush(); v.stdin.Close(); } } catch (Exception e) { v.err = e.Message; }
+            }
+            StringBuilder note = new StringBuilder();
+            for (int i = 0; i < _views.Count; i++)
+            {
+                View v = _views[i];
+                try { if (v.proc != null) v.proc.WaitForExit(); } catch (Exception) { }
+                int code = -1;
+                try { code = v.proc.ExitCode; } catch (Exception) { }
+                File.WriteAllText(Path.Combine(_dir, _prefix + "_" + v.name + "_frames.csv"), _csv.ToString(),
+                                  new UTF8Encoding(false));
+                note.Append("\n  ").Append(v.mp4).Append("（").Append(v.w).Append('×').Append(v.h)
+                    .Append("・ffmpeg 終了コード ").Append(code)
+                    .Append(v.err.Length > 0 ? "・" + v.err : "")
+                    .Append(v.ffLog.Length > 0 ? "・" + v.ffLog.ToString().Trim() : "").Append('）');
+                if (v.rt != null) v.rt.Release();
+            }
+            Log("撮り終わりました: " + _frames + " コマ" + note);
+        }
+
+        void OnApplicationQuit() { Close(); }
+        void OnDestroy() { Close(); }
+
+        static string Find(string[] argv, string name)
+        {
+            for (int i = 0; i < argv.Length - 1; i++)
+                if (argv[i] == name) return argv[i + 1];
+            return null;
+        }
+
+        static float F(string s, float def)
+        {
+            float v;
+            return s != null && float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out v) ? v : def;
+        }
+
+        static void Log(string msg) { Debug.Log("[ServiceSim/rec] " + msg); }
+    }
+}

--- /dev/null (cs:40 には無い)
+++ Assets/ServiceSim/Recording/SimTimeRecorder.cs.meta#cs:41
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: dd1c5fd6727c74a45aa312ded978a13e
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

--- /dev/null (cs:40 には無い)
+++ Assets/ServiceSim/Editor/RecordBuild.cs#cs:41
@@ -0,0 +1,45 @@
+using System;
+using UnityEditor;
+using UnityEngine;
+
+namespace ServiceSim.EditorTools
+{
+    /// <summary>
+    /// 録画に使う実行ファイルを、夜の実行ファイル（Builds/headless）とは別の場所に作る
+    /// （2026-10-29・依頼 26。中身は BuildHeadless と同じ。置き場所だけ違う）。
+    ///
+    /// 夜の実行係が使う Builds/headless を、録画のための版で上書きしないため。
+    /// 回すときは -nographics を付けない（-batchmode だけ）。付けると描けないので撮れない。
+    ///
+    ///   Unity -batchmode -quit -projectPath … \
+    ///     -executeMethod ServiceSim.EditorTools.RecordBuild.Build -ssBuildOut Builds/rec_after
+    /// </summary>
+    public static class RecordBuild
+    {
+        public static void Build()
+        {
+            string outDir = Find(Environment.GetCommandLineArgs(), "-ssBuildOut");
+            if (string.IsNullOrEmpty(outDir) || outDir.TrimEnd('/') == BuildHeadless.OutDir)
+            {
+                Debug.LogError("[ServiceSim] -ssBuildOut に Builds/headless 以外の置き場所を入れてください");
+                EditorApplication.Exit(1);
+                return;
+            }
+            string err;
+            if (!BuildHeadless.BuildTo(outDir, out err))
+            {
+                Debug.LogError("[ServiceSim] 録画用の実行ファイルを作れませんでした\n  " + err);
+                EditorApplication.Exit(1);
+                return;
+            }
+            EditorApplication.Exit(0);
+        }
+
+        static string Find(string[] argv, string name)
+        {
+            for (int i = 0; i < argv.Length - 1; i++)
+                if (argv[i] == name) return argv[i + 1];
+            return null;
+        }
+    }
+}

--- /dev/null (cs:40 には無い)
+++ Assets/ServiceSim/Editor/RecordBuild.cs.meta#cs:41
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 27b57d3c45af542a18bb1d5b524e2f63
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
```

### 光の角度と静止画の回を足したときの差分：cs:41 → cs:42

```diff
# cm diff cs:41 cs:42 （ServiceSim）
# 1. cm diff の出力そのまま

C "Assets/ServiceSim/Recording/SimTimeRecorder.cs"

# 2. 中身の違い（補足。上の各ファイルを cm cat <path>#cs:N で取り出し、diff -u で比べたもの）

--- Assets/ServiceSim/Recording/SimTimeRecorder.cs#cs:41
+++ Assets/ServiceSim/Recording/SimTimeRecorder.cs#cs:42
@@ -24,8 +24,11 @@
     ///   ・自分で作った真上のカメラ（正投影・ふだんは切）を、撮るコマだけ
     ///     RenderTexture に描いて読み、ffmpeg に生のまま渡して mp4 にする。
     ///     画面の上の文字（OnGUI）は RenderTexture に入らない。
-    ///   ・店のもの・人・ロボット・光・ほかのカメラには触らない。
+    ///   ・店のもの・人・ロボット・ほかのカメラには触らない。
     ///     見た目は Main Camera の設定（背景色・映すもの）を写して使う。
+    ///   ・光：影を落とす Directional Light（Setup の Sun）の高さだけを上げて影を短くする
+    ///     （2026-10-29・依頼 28。地面からの角度 52° → 75°。方角・やわらかさ・濃さ・色・明るさは今のまま）。
+    ///     撮り始めるときに変える。-ssRecord の無い回（夜の回・確かめの回・エディタの Play）では変わらない。
     ///
     /// 引数
     ///   -ssRecord {名前の頭}     … mp4 の名前の頭（例 20261029_1930_lpdemo_after）
@@ -33,6 +36,11 @@
     ///   -ssRecFps 30 -ssRecCrf 16
     ///   -ssRecFfmpeg {ffmpeg のパス}
     ///   -ssRecView name,x0,x1,y0,y1,w,h   … 撮る範囲（店の座標 m）と大きさ。何本でも
+    ///   -ssRecSunPitch 75                 … 録画のときの光の地面からの角度（既定 75。依頼 28）
+    ///   -ssRecStillAt 1500 -ssRecStillPitches 70,75,80
+    ///                                     … 静止画だけ書く回（依頼 28）。店の時刻がこの秒に来たコマを、
+    ///                                       光の角度ごとに PNG で書いて、回を途中で終える（mp4 は作らない。
+    ///                                       台帳には書かれず、記録は {run_stem}_stopped に残る）
     ///
     /// 置き場所：その回の出力フォルダ（HeadlessRun が決めた run_stem）の下の recordings/。
     ///   recordings/ がもうあれば撮らない（上書きしない）。
@@ -40,6 +48,8 @@
     public class SimTimeRecorder : MonoBehaviour
     {
         const float CamHeight = 60f;
+        /// <summary>録画のときの光の地面からの角度（依頼 28。ふだんの Sun は 52°）</summary>
+        const float DefaultSunPitch = 75f;
 
         class View
         {
@@ -59,6 +69,10 @@
         string _prefix = "", _ffmpeg = "ffmpeg";
         float _from = 900f, _to = 1650f, _step = 0.2f;
         int _fps = 30, _crf = 16;
+        float _sunPitch = DefaultSunPitch;
+        float _stillAt = -1f;                       // 0 以上なら静止画だけ書く回
+        readonly List<float> _stillPitches = new List<float>();
+        Light _sun;
         readonly List<View> _views = new List<View>();
 
         ExperimentRunner _runner;
@@ -93,6 +107,16 @@
             _step = F(Find(argv, "-ssRecStep"), _step);
             _fps = (int)F(Find(argv, "-ssRecFps"), _fps);
             _crf = (int)F(Find(argv, "-ssRecCrf"), _crf);
+            _sunPitch = F(Find(argv, "-ssRecSunPitch"), _sunPitch);
+            _stillAt = F(Find(argv, "-ssRecStillAt"), -1f);
+            string pitches = Find(argv, "-ssRecStillPitches");
+            if (pitches != null)
+                foreach (string p in pitches.Split(',')) _stillPitches.Add(F(p, _sunPitch));
+            if (_stillAt >= 0f)
+            {
+                if (_stillPitches.Count == 0) _stillPitches.Add(_sunPitch);
+                _from = _stillAt; _to = _stillAt;
+            }
 
             for (int i = 0; i < argv.Length - 1; i++)
             {
@@ -123,6 +147,12 @@
                 _failed = true;
                 return;
             }
+            if (_stillAt >= 0f)
+            {
+                Log("静止画だけ書きます: 店の時刻 " + _stillAt + " 秒・光の角度 " + string.Join(",", _stillPitches) +
+                    "°・" + _views.Count + " 本。書いたら回を途中で終えます");
+                return;
+            }
             Log("撮ります: 店の時刻 " + _from + "〜" + _to + " 秒・" + _step + " 秒ごと・" + _fps + " コマ/秒・CRF " + _crf +
                 "・" + _views.Count + " 本");
         }
@@ -143,6 +173,8 @@
 
             if (!_started && !Begin()) { _failed = true; return; }
 
+            if (_stillAt >= 0f) { WriteStills(t); return; }
+
             for (int i = 0; i < _views.Count; i++) Grab(_views[i]);
             _csv.Write(_frames.ToString(CultureInfo.InvariantCulture));
             _csv.Write(',');
@@ -173,6 +205,10 @@
             }
             Directory.CreateDirectory(_dir);
 
+            _sun = FindSun();
+            if (_sun == null) Log("影を落とす Directional Light が1つに決まりません。光は変えずに撮ります");
+            else SetSunPitch(_sunPitch);
+
             Camera main = Camera.main;
             for (int i = 0; i < _views.Count; i++)
             {
@@ -195,6 +231,7 @@
                 c.targetTexture = v.rt;
                 v.cam = c;
                 v.tex = new Texture2D(v.w, v.h, TextureFormat.RGB24, false);
+                if (_stillAt >= 0f) continue;   // 静止画だけの回は ffmpeg を立てない
 
                 v.mp4 = Path.Combine(_dir, _prefix + "_" + v.name + ".mp4");
                 string args = "-hide_banner -loglevel error -y -f rawvideo -pix_fmt rgb24 -s " + v.w + "x" + v.h +
@@ -220,6 +257,62 @@
             return true;
         }
 
+        /// <summary>影を落とす Directional Light（Setup の Sun）。1つに決まらなければ null</summary>
+        static Light FindSun()
+        {
+            Light found = null;
+            foreach (Light l in FindObjectsOfType<Light>())
+            {
+                if (l.type != LightType.Directional || l.shadows == LightShadows.None) continue;
+                if (found != null) return null;
+                found = l;
+            }
+            return found;
+        }
+
+        /// <summary>地面からの角度だけを変える（方角＝y の回転・z の回転はそのまま）</summary>
+        void SetSunPitch(float pitch)
+        {
+            if (_sun == null) return;
+            Vector3 e = _sun.transform.eulerAngles;
+            float before = e.x;
+            _sun.transform.rotation = Quaternion.Euler(pitch, e.y, e.z);
+            Log("光の角度: " + _sun.name + " " + before.ToString("0.#", CultureInfo.InvariantCulture) + "° → " +
+                pitch.ToString("0.#", CultureInfo.InvariantCulture) + "°（方角 " +
+                e.y.ToString("0.#", CultureInfo.InvariantCulture) + "° のまま）");
+        }
+
+        /// <summary>静止画だけの回：光の角度ごとに PNG を書いて、回を途中で終える</summary>
+        void WriteStills(float t)
+        {
+            _done = true;
+            StringBuilder note = new StringBuilder();
+            for (int k = 0; k < _stillPitches.Count; k++)
+            {
+                float pitch = _stillPitches[k];
+                SetSunPitch(pitch);
+                for (int i = 0; i < _views.Count; i++)
+                {
+                    View v = _views[i];
+                    v.cam.Render();
+                    RenderTexture prev = RenderTexture.active;
+                    RenderTexture.active = v.rt;
+                    v.tex.ReadPixels(new Rect(0, 0, v.w, v.h), 0, 0, false);
+                    v.tex.Apply(false);
+                    RenderTexture.active = prev;
+                    string png = Path.Combine(_dir, _prefix + "_" + v.name + "_t" +
+                                              t.ToString("0.##", CultureInfo.InvariantCulture) + "_sun" +
+                                              pitch.ToString("0.#", CultureInfo.InvariantCulture) + ".png");
+                    File.WriteAllBytes(png, v.tex.EncodeToPNG());
+                    note.Append("\n  ").Append(png);
+                }
+            }
+            for (int i = 0; i < _views.Count; i++) if (_views[i].rt != null) _views[i].rt.Release();
+            Log("静止画を書きました（店の時刻 " + t.ToString("0.###", CultureInfo.InvariantCulture) + " 秒）" + note +
+                "\n  回を途中で終えます（台帳には書きません）");
+            Application.Quit(0);
+        }
+
         void Grab(View v)
         {
             if (v.stdin == null) return;
```

### 静止画の回の間違いを直したときの差分：cs:42 → cs:43

```diff
# cm diff cs:42 cs:43 （ServiceSim）
# 1. cm diff の出力そのまま

C "Assets/ServiceSim/Recording/SimTimeRecorder.cs"

# 2. 中身の違い（補足。上の各ファイルを cm cat <path>#cs:N で取り出し、diff -u で比べたもの）

--- Assets/ServiceSim/Recording/SimTimeRecorder.cs#cs:42
+++ Assets/ServiceSim/Recording/SimTimeRecorder.cs#cs:43
@@ -115,7 +115,7 @@
             if (_stillAt >= 0f)
             {
                 if (_stillPitches.Count == 0) _stillPitches.Add(_sunPitch);
-                _from = _stillAt; _to = _stillAt;
+                _from = _stillAt; _to = _stillAt + _step;   // 1コマだけ撮る（_to <= _from の確かめに掛からないように）
             }
 
             for (int i = 0; i < argv.Length - 1; i++)
@@ -138,13 +138,13 @@
                 _fOffset == null || _fJobRunning == null || _fRunDir == null)
             {
                 Log("撮れません（引数か、ExperimentRunner / HeadlessRun の中の名前が合いません）。この回は撮らずに回します");
-                _failed = true;
+                FailSetup();
                 return;
             }
             if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Null)
             {
                 Log("描画の装置がありません（-nographics では撮れません）。この回は撮らずに回します");
-                _failed = true;
+                FailSetup();
                 return;
             }
             if (_stillAt >= 0f)
@@ -171,7 +171,7 @@
             if (target > _to + 1e-3f) { Close(); return; }
             if (t < target - 1e-3f) return;
 
-            if (!_started && !Begin()) { _failed = true; return; }
+            if (!_started && !Begin()) { _failed = true; QuitIfStill(); return; }
 
             if (_stillAt >= 0f) { WriteStills(t); return; }
 
@@ -255,8 +255,22 @@
             _csv.Write("frame,t_s\n");
             Log("撮り始めました: " + _dir);
             return true;
+        }
+
+        void FailSetup()
+        {
+            _failed = true;
+            QuitIfStill();
         }
 
+        /// <summary>静止画だけの回は、撮れないなら最後まで回さずに終える（台帳に行を残さない）</summary>
+        void QuitIfStill()
+        {
+            if (_stillAt < 0f) return;
+            Log("静止画だけの回なので、撮れないまま回さずに終えます");
+            Application.Quit(1);
+        }
+
         /// <summary>影を落とす Directional Light（Setup の Sun）。1つに決まらなければ null</summary>
         static Light FindSun()
         {
```

### 録画の実行ファイルを作って回したときのコマンド commands.txt

```
====================================================================
LP 用の録画：実行ファイルを作って回したときのコマンド（依頼26・依頼28）
書いた日：2026-10-29（依頼29）
====================================================================

出どころ
  ・依頼26・28 の作業の記録（Claude Code のセッション記録）から、実際に打ったコマンドを
    そのまま写した。セッション：依頼26＝685755d2-3a74-45bf-82aa-e5af669ecbb8、
    依頼28＝5a3ac635-5f19-4f05-865f-416886b4b7cc
    （~/.claude/projects/-Users-shimazueiichi-Robotics-UX-ServiceSim/ の .jsonl）
  ・r2r2r-scripts/logs/ には依頼26・28 の記録はなかった（20261029 の2本は依頼25・27 のもの）
  ・時刻は日本時間。確かめ・結果の表示（grep・wc など）は、コマンドの中に付いているまま残した
  ・作業フォルダはどれも /Users/shimazueiichi/Robotics_UX/ServiceSim
  ・$(date +%H%M) などで決めた時刻は、出力のファイル名・build_id から分かるものを（ ）に書いた

共通
  U=/Applications/Unity/Hub/Editor/2022.3.62f3/Unity.app/Contents/MacOS/Unity
  FF=/opt/homebrew/Caskroom/miniforge/base/envs/ros_env/bin/ffmpeg   （ffmpeg 7.1.1。PATH には無かった）
  実行ファイルの作り方：$U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.RecordBuild.Build -ssBuildOut <置き場所>
  回し方：-batchmode だけ（-nographics は付けない。描くため）


====================================================================
依頼26（cs:41。映像A 寄り・映像B 全体の2本ずつ）
====================================================================

--- 20:47 録画のしくみの試し（check-in 前・cs:40 に保留あり。店の時刻 20〜26 秒だけ撮って止めた。台帳には書いていない）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; U=/Applications/Unity/Hub/Editor/2022.3.62f3/Unity.app/Contents/MacOS/Unity; ls $U && $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.RecordBuild.Build -ssBuildOut Builds/rec_test -logFile /tmp/rec_test_build.log; echo exit=$?; grep -n "error CS\|Build Finished\|録画用\|画面なしの実行ファイルを作りました" /tmp/rec_test_build.log | head; cat Builds/rec_test/build_info.json

cd /Users/shimazueiichi/Robotics_UX/ServiceSim; FF=/opt/homebrew/Caskroom/miniforge/base/envs/ros_env/bin/ffmpeg; L=/Volumes/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv; wc -l < $L
Builds/rec_test/ServiceSim.app/Contents/MacOS/ServiceSim -batchmode -logFile /tmp/rectest.log -ssCondition fam_p0_s2_r2_a19 -ssSeed 0 -ssLabel rectest -ssNight 20261029 -ssParallel 1 -ssRunning 1 -ssPassBy on -ssRuleB off -ssStandbyReturn on -ssRecord test -ssRecFrom 20 -ssRecTo 26 -ssRecStep 0.2 -ssRecFfmpeg $FF -ssRecView A_close,3.0,10.0,10.5,15.0,1680,1080 -ssRecView B_whole,-0.8,24.8,-0.8,20.0,1920,1560 &
P=$!; for i in $(seq 1 120); do sleep 1; grep -q "撮り終わりました\|撮れません\|撮りません" /tmp/rectest.log && break; done; kill $P; sleep 2; grep "ServiceSim/rec\|graphics\|Metal\|GfxDevice" /tmp/rectest.log | head -20; wc -l < $L; ls -la /Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261029_rectest*/recordings/

--- 20:49 試しをもう一度（作り直して同じ引数で回した）
rm -r /Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261029_rectest_cs40_fam_p0_r2_s0_passon_sbret_stopped; cd /Users/shimazueiichi/Robotics_UX/ServiceSim; U=/Applications/Unity/Hub/Editor/2022.3.62f3/Unity.app/Contents/MacOS/Unity; $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.RecordBuild.Build -ssBuildOut Builds/rec_test -logFile /tmp/rec_test_build.log; echo exit=$?; grep -c "error CS" /tmp/rec_test_build.log
FF=/opt/homebrew/Caskroom/miniforge/base/envs/ros_env/bin/ffmpeg
Builds/rec_test/ServiceSim.app/Contents/MacOS/ServiceSim -batchmode -logFile /tmp/rectest.log -ssCondition fam_p0_s2_r2_a19 -ssSeed 0 -ssLabel rectest -ssNight 20261029 -ssParallel 1 -ssRunning 1 -ssPassBy on -ssRuleB off -ssStandbyReturn on -ssRecord test -ssRecFrom 20 -ssRecTo 26 -ssRecStep 0.2 -ssRecFfmpeg $FF -ssRecView A_close,3.0,10.0,10.5,15.0,1680,1080 -ssRecView B_whole,-0.8,24.8,-0.8,20.0,1920,1560 &
P=$!; for i in $(seq 1 120); do sleep 1; grep -q "撮り終わりました\|撮れません\|撮りません" /tmp/rectest.log && break; done; kill $P; sleep 3; grep -A3 "ServiceSim/rec" /tmp/rectest.log | head -20; D=$(ls -d /Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261029_rectest*); echo $D; ls -la $D/recordings 2>/dev/null || ls -la $D*/
  （注：このあと rectest のフォルダと Builds/rec_test を消すコマンドを打ったが、許可が出ず消えていない。
    2026-10-29 22:58 の時点で KIOXIA に 20261029_rectest_cs40_… と …_stopped の2フォルダが残っている）

--- 20:50 check-in（cs:41）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; A=Assets/ServiceSim; cm add $A/Editor/RecordBuild.cs $A/Editor/RecordBuild.cs.meta $A/Recording/SimTimeRecorder.cs $A/Recording/SimTimeRecorder.cs.meta && cm ci $A/Editor/RecordBuild.cs $A/Editor/RecordBuild.cs.meta $A/Recording/SimTimeRecorder.cs $A/Recording/SimTimeRecorder.cs.meta -c="LP用の録画のしくみを足す：SimTimeRecorder（引数 -ssRecord のときだけ立つ。店の時刻に合わせて真上の正投影カメラで撮り ffmpeg で mp4 にする）と RecordBuild（録画用の実行ファイルを Builds/headless とは別の場所に作る）。店・人・ロボットのコードと設定は変えていない"; cm status --machinereadable | head -3

--- 20:50 直した後：実行ファイルを作る（build_id cs41_20261029_2050）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; U=/Applications/Unity/Hub/Editor/2022.3.62f3/Unity.app/Contents/MacOS/Unity; $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.RecordBuild.Build -ssBuildOut Builds/rec_after -logFile /tmp/rec_after_build.log; echo exit=$?; grep -c "error CS" /tmp/rec_after_build.log; cat Builds/rec_after/build_info.json | grep -E "build_id|unity_cs|pending"; cm status --short

--- 20:50 直した後：回す（ST=20261029_2050 → 20261029_lpdemo_cs41_fam_p0_r2_s0_passon_sbret）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; FF=/opt/homebrew/Caskroom/miniforge/base/envs/ros_env/bin/ffmpeg; ST=$(date +%Y%m%d_%H%M); echo $ST > /tmp/after_stamp; ls -d /Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261029_lpdemo_* 2>/dev/null; /usr/bin/time -p Builds/rec_after/ServiceSim.app/Contents/MacOS/ServiceSim -batchmode -logFile /tmp/lpdemo_after.log -ssCondition fam_p0_s2_r2_a19 -ssSeed 0 -ssLabel lpdemo -ssNight 20261029 -ssParallel 1 -ssRunning 1 -ssPassBy on -ssRuleB off -ssStandbyReturn on -ssRecord ${ST}_lpdemo_after -ssRecFrom 900 -ssRecTo 1650 -ssRecStep 0.2 -ssRecFps 30 -ssRecCrf 16 -ssRecFfmpeg $FF -ssRecView A_close,3.0,10.0,10.5,15.0,1680,1080 -ssRecView B_whole,-0.8,24.8,-0.8,20.0,1920,1560; echo exit=$?; grep -A3 "ServiceSim/rec\|おわり:" /tmp/lpdemo_after.log | grep -v memorysetup | head -20

--- 20:54 直す前：site_copy_dffc100b269c の 14 ファイルを写す（保留になる）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; I="$HOME/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity/site_copy_dffc100b269c"; cp "$I/serving-robot-params.json" Assets/ServiceSim/Experiment/serving-robot-params.json; cp "$I/summary.json" Assets/ServiceSim/Store/Layouts/summary.json; for f in "$I"/unity/*.json; do cp "$f" Assets/ServiceSim/Store/Layouts/; done; cmp "$I/serving-robot-params.json" Assets/ServiceSim/Experiment/serving-robot-params.json && echo ok; for f in "$I"/unity/*.json; do cmp "$f" Assets/ServiceSim/Store/Layouts/$(basename "$f") || echo BAD; done; for f in $(cm diff cs:39 cs:40 | sed 's/^C "//;s/"$//'); do cm cat "serverpath:/${f#Assets/}#cs:39" >/dev/null 2>&1; done; cm status --short | wc -l

--- 20:55 直す前：②を建てて確かめる（JSON Store・Map Planner）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; U=/Applications/Unity/Hub/Editor/2022.3.62f3/Unity.app/Contents/MacOS/Unity; $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.ServiceSimJsonStore.CheckP0 -logFile /tmp/before_jsonstore.log; echo exit=$?; $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.ServiceSimMapPlanner.CheckP0 -logFile /tmp/before_mapplanner.log; echo exit=$?; grep -c "error CS" /tmp/before_jsonstore.log /tmp/before_mapplanner.log; grep -n "合わない\|引けない卓\|卓：\|版 \|generation\|判定\|合格\|Error\|エラー" /tmp/before_jsonstore.log /tmp/before_mapplanner.log | grep -v memorysetup | head -40

--- 20:55 直す前：実行ファイルを作る（build_id cs41_20261029_2055）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; U=/Applications/Unity/Hub/Editor/2022.3.62f3/Unity.app/Contents/MacOS/Unity; $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.RecordBuild.Build -ssBuildOut Builds/rec_before -logFile /tmp/rec_before_build.log; echo exit=$?; grep -c "error CS" /tmp/rec_before_build.log; grep -E "build_id|unity_cs|pending" Builds/rec_before/build_info.json

--- 20:55 直す前：回す（ST=20261029_2055 → 20261029_lpdemobefore_cs41_fam_p0_r2_s0_passon_sbret）
  （このあとに続く python の集計（stops.csv の数え上げ）は省いた）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; FF=/opt/homebrew/Caskroom/miniforge/base/envs/ros_env/bin/ffmpeg; ST=$(date +%Y%m%d_%H%M); /usr/bin/time -p Builds/rec_before/ServiceSim.app/Contents/MacOS/ServiceSim -batchmode -logFile /tmp/lpdemo_before.log -ssCondition fam_p0_s2_r2_a19 -ssSeed 0 -ssLabel lpdemobefore -ssNight 20261029 -ssParallel 1 -ssRunning 1 -ssPassBy on -ssRuleB off -ssStandbyReturn on -ssRecord ${ST}_lpdemo_before -ssRecFrom 900 -ssRecTo 1650 -ssRecStep 0.2 -ssRecFps 30 -ssRecCrf 16 -ssRecFfmpeg $FF -ssRecView A_close,3.0,10.0,10.5,15.0,1680,1080 -ssRecView B_whole,-0.8,24.8,-0.8,20.0,1920,1560 2>&1 | tail -3; grep -A3 "ServiceSim/rec\] 撮り\|おわり:" /tmp/lpdemo_before.log | head -12

--- 20:59 直す前の保留を戻す
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; grep "おわり:" -A1 /tmp/lpdemo_before.log /tmp/lpdemo_after.log; cm status --short > /tmp/pend.txt; wc -l < /tmp/pend.txt; cm undo $(cat /tmp/pend.txt | tr '\n' ' ') 2>&1 | tail -3; cm status --machinereadable

--- 21:00 今の版で②を建て直し、触った .asset を戻す
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; U=/Applications/Unity/Hub/Editor/2022.3.62f3/Unity.app/Contents/MacOS/Unity; $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.ServiceSimJsonStore.CheckP0 -logFile /tmp/restore_jsonstore.log; echo exit=$?; grep -m2 "版の番号 (meta.generation)\|合わないもの" /tmp/restore_jsonstore.log; cm status --short > /tmp/pend2.txt; cat /tmp/pend2.txt; [ -s /tmp/pend2.txt ] && cm undo $(cat /tmp/pend2.txt | tr '\n' ' ') | tail -1; cm status --machinereadable; grep -m1 "generation\|版" "$HOME/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity/screens/20261029_p0_memory_json_check.txt"


====================================================================
依頼28（cs:42・cs:43。光の角度を変え、映像A だけ撮り直し）
====================================================================

--- 22:05 SimTimeRecorder.cs を書き換えたあと、コンパイルの確かめ（作った Builds/rec2_compiletest は使っていない）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; U=/Applications/Unity/Hub/Editor/2022.3.62f3/Unity.app/Contents/MacOS/Unity; $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.RecordBuild.Build -ssBuildOut Builds/rec2_compiletest -logFile /tmp/rec2_compiletest.log; echo exit=$?; grep -n "error CS\|ServiceSim\]" /tmp/rec2_compiletest.log | head
  （書き換えは python の置き換えで行った。中身は diff_cs41_cs42.txt と同じなので省いた）

--- 22:05 check-in（cs:42）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; cm checkin Assets/ServiceSim/Recording/SimTimeRecorder.cs -c="録画のときだけ光（Sun）の高さを上げて影を短くする（依頼28。地面からの角度 52°→75°・方角などは今のまま。-ssRecord の無い回は変わらない）。静止画だけ書く回（-ssRecStillAt）を足す" 2>&1 | tail -3; cm status --machinereadable

--- 22:05 静止画用：実行ファイルを作る（cs:42・build_id cs42_20261029_2205）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; U=/Applications/Unity/Hub/Editor/2022.3.62f3/Unity.app/Contents/MacOS/Unity; $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.RecordBuild.Build -ssBuildOut Builds/rec2_after -logFile /tmp/rec2_after.log; echo exit=$?; cat Builds/rec2_after/build_info.json; cm status --machinereadable

--- 22:06 静止画の回（cs:42。まちがい：撮れずに最後まで回り、台帳に 20261029_lpdemo2still_cs42_… の1行が書かれた）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; FF=/opt/homebrew/Caskroom/miniforge/base/envs/ros_env/bin/ffmpeg; HM=$(date +%H%M); echo $HM; Builds/rec2_after/ServiceSim.app/Contents/MacOS/ServiceSim -batchmode -logFile /tmp/lpdemo2_still.log -ssCondition fam_p0_s2_r2_a19 -ssSeed 0 -ssNight 20261029 -ssParallel 1 -ssRunning 1 -ssPassBy on -ssRuleB off -ssStandbyReturn on -ssLabel lpdemo2still -ssRecord 20261029_${HM}_lpdemo2_after_still -ssRecStillAt 1500 -ssRecStillPitches 70,75,80 -ssRecFfmpeg $FF -ssRecView A_close,3.0,10.0,10.5,15.0,1680,1080; echo exit=$?; grep -n "rec\]\|途中で止め\|headless\]" /tmp/lpdemo2_still.log | head -20; wc -l /Volumes/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv

--- 22:08〜22:18 直してコンパイルの確かめ（書き換えは python の置き換え。中身は diff_cs42_cs43.txt と同じ）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; U=/Applications/Unity/Hub/Editor/2022.3.62f3/Unity.app/Contents/MacOS/Unity; $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.RecordBuild.Build -ssBuildOut Builds/rec2_compiletest -logFile /tmp/rec2_compiletest2.log; echo exit=$?; grep -n "error CS" /tmp/rec2_compiletest2.log | head

--- 22:19 check-in（cs:43）と、実行ファイルを作る（build_id cs43_20261029_2219）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; cm checkin Assets/ServiceSim/Recording/SimTimeRecorder.cs -c="静止画だけの回が撮れずに最後まで回ってしまうのを直す（依頼28。始まりと終わりの時刻を1コマ分あける・撮れないときは回さずに終える）" 2>&1 | tail -1; cm status --machinereadable; U=/Applications/Unity/Hub/Editor/2022.3.62f3/Unity.app/Contents/MacOS/Unity; $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.RecordBuild.Build -ssBuildOut Builds/rec2_after43 -logFile /tmp/rec2_after43.log; echo exit=$?; grep -E "build_id|unity_cs|pending" Builds/rec2_after43/build_info.json; cm status --machinereadable

--- 22:19 静止画の回（cs:43。HM=2219 → 20261029_lpdemo2still_cs43_…_stopped・PNG 3 枚。台帳には書かれない）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; FF=/opt/homebrew/Caskroom/miniforge/base/envs/ros_env/bin/ffmpeg; HM=$(date +%H%M); echo $HM; Builds/rec2_after43/ServiceSim.app/Contents/MacOS/ServiceSim -batchmode -logFile /tmp/lpdemo2_still2.log -ssCondition fam_p0_s2_r2_a19 -ssSeed 0 -ssNight 20261029 -ssParallel 1 -ssRunning 1 -ssPassBy on -ssRuleB off -ssStandbyReturn on -ssLabel lpdemo2still -ssRecord 20261029_${HM}_lpdemo2_after_still -ssRecStillAt 1500 -ssRecStillPitches 70,75,80 -ssRecFfmpeg $FF -ssRecView A_close,3.0,10.0,10.5,15.0,1680,1080; echo exit=$?; grep -A4 "rec\]" /tmp/lpdemo2_still2.log | head -30; grep "途中で止め\|おわり" /tmp/lpdemo2_still2.log; wc -l /Volumes/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv

--- 22:20 直した後：回す（このフォルダの録画。HM=2220 → 20261029_2220_lpdemo2_after_A_close.mp4）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; ls -d /Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261029_lpdemo2_* 2>/dev/null; FF=/opt/homebrew/Caskroom/miniforge/base/envs/ros_env/bin/ffmpeg; HM=$(date +%H%M); echo $HM; Builds/rec2_after43/ServiceSim.app/Contents/MacOS/ServiceSim -batchmode -logFile /tmp/lpdemo2_after.log -ssCondition fam_p0_s2_r2_a19 -ssSeed 0 -ssNight 20261029 -ssParallel 1 -ssRunning 1 -ssPassBy on -ssRuleB off -ssStandbyReturn on -ssLabel lpdemo2 -ssRecord 20261029_${HM}_lpdemo2_after -ssRecFrom 900 -ssRecTo 1650 -ssRecStep 0.2 -ssRecFps 30 -ssRecCrf 16 -ssRecSunPitch 75 -ssRecFfmpeg $FF -ssRecView A_close,3.0,10.0,10.5,15.0,1680,1080 >/dev/null; echo exit=$?; grep -A2 "rec\]\|headless\] おわり" /tmp/lpdemo2_after.log | grep -v "^  条件" | head -20; wc -l /Volumes/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv

--- 22:23 直す前：screens の控えをとり、site_copy_dffc100b269c の 14 ファイルを写す（保留になる）
R="/Users/shimazueiichi/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity"; mkdir -p /tmp/lp28/screens_bak; cd "$R/screens"; cp -p 20261029_family_v0_* 20261029_p0_memory_json_check.txt 20261029_p0_memory_table_dist.csv /tmp/lp28/screens_bak/; ls /tmp/lp28/screens_bak | wc -l
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; S="$R/site_copy_dffc100b269c"; L=Assets/ServiceSim/Store/Layouts
cp "$S/serving-robot-params.json" Assets/ServiceSim/Experiment/serving-robot-params.json; cp "$S/summary.json" $L/summary.json; for f in "$S"/unity/*.json; do cp "$f" $L/; done
ok=0; cmp -s "$S/serving-robot-params.json" Assets/ServiceSim/Experiment/serving-robot-params.json && ok=$((ok+1)); cmp -s "$S/summary.json" $L/summary.json && ok=$((ok+1)); for f in "$S"/unity/*.json; do cmp -s "$f" $L/$(basename $f) && ok=$((ok+1)); done; echo same=$ok
for f in Assets/ServiceSim/Experiment/serving-robot-params.json $L/*.json; do cm cat "$f#cs:39" > /tmp/lp28/c39 2>/dev/null && cmp -s /tmp/lp28/c39 $f || echo "DIFF cs39 $f"; done; cm status --machinereadable | wc -l

--- 22:23 直す前：②を建てて確かめる
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; U=/Applications/Unity/Hub/Editor/2022.3.62f3/Unity.app/Contents/MacOS/Unity; $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.ServiceSimJsonStore.CheckP0 -logFile /tmp/before2_jsonstore.log; echo js=$?; $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.ServiceSimMapPlanner.CheckP0 -logFile /tmp/before2_mapplanner.log; echo mp=$?; grep -n "版\|合わない\|つながっ\|引けない\|通った\|通りません\|乗らない" /tmp/before2_jsonstore.log /tmp/before2_mapplanner.log | cut -c1-220 | head -20; cm status --machinereadable

--- 22:24 直す前：実行ファイルを作って回す（build_id cs43_20261029_2224・HM=2224 → 20261029_lpdemo2before_cs43_…）
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; U=/Applications/Unity/Hub/Editor/2022.3.62f3/Unity.app/Contents/MacOS/Unity; $U -batchmode -quit -projectPath "$PWD" -executeMethod ServiceSim.EditorTools.RecordBuild.Build -ssBuildOut Builds/rec2_before43 -logFile /tmp/rec2_before43.log; echo exit=$?; grep -E "build_id|unity_cs" Builds/rec2_before43/build_info.json
FF=/opt/homebrew/Caskroom/miniforge/base/envs/ros_env/bin/ffmpeg; HM=$(date +%H%M); echo $HM; Builds/rec2_before43/ServiceSim.app/Contents/MacOS/ServiceSim -batchmode -logFile /tmp/lpdemo2_before.log -ssCondition fam_p0_s2_r2_a19 -ssSeed 0 -ssNight 20261029 -ssParallel 1 -ssRunning 1 -ssPassBy on -ssRuleB off -ssStandbyReturn on -ssLabel lpdemo2before -ssRecord 20261029_${HM}_lpdemo2_before -ssRecFrom 900 -ssRecTo 1650 -ssRecStep 0.2 -ssRecFps 30 -ssRecCrf 16 -ssRecSunPitch 75 -ssRecFfmpeg $FF -ssRecView A_close,3.0,10.0,10.5,15.0,1680,1080 >/dev/null; echo exit=$?; grep -A1 "rec\] 光\|rec\] 撮り終\|headless\] おわり" /tmp/lpdemo2_before.log | cut -c1-250; wc -l /Volumes/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv

--- 22:27 直す前の保留を戻す
cd /Users/shimazueiichi/Robotics_UX/ServiceSim; cm status --machinereadable | grep "^CH" | awk '{print $2}' > /tmp/lp28/pend.txt; wc -l < /tmp/lp28/pend.txt; cm undo $(cat /tmp/lp28/pend.txt) 2>&1 | tail -2; cm status --machinereadable
R="/Users/shimazueiichi/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity"; S="$R/site_copy_08db8a0d067c"; L=Assets/ServiceSim/Store/Layouts; n=0; cmp -s "$S/serving-robot-params.json" Assets/ServiceSim/Experiment/serving-robot-params.json && n=$((n+1)); cmp -s "$S/summary.json" $L/summary.json && n=$((n+1)); for f in "$S"/unity/*.json; do cmp -s "$f" $L/$(basename $f) && n=$((n+1)); done; echo same_08db=$n

--- 22:27 screens を控えから書き戻す
R="/Users/shimazueiichi/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity"; cd "$R/screens"; for f in /tmp/lp28/screens_bak/*; do b=$(basename "$f"); cmp -s "$f" "$b" && echo "same $b" || echo "changed $b"; done; ls -lT | awk '$7==29 && $8 ~ /^22:/' | awk '{print $NF}'
R="/Users/shimazueiichi/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity"; cp -p /tmp/lp28/screens_bak/* "$R/screens/"; cd "$R/screens"; n=0; for f in /tmp/lp28/screens_bak/*; do cmp -s "$f" "$(basename "$f")" && n=$((n+1)); done; echo restored_same=$n; grep -m2 "版の番号\|合わないもの" 20261029_p0_memory_json_check.txt


====================================================================
拾えなかったところ
====================================================================

  ・セッション記録に残っているのは Claude Code が打ったコマンドだけ。人が手で打ったものがあったかは分からない
```

### 依頼文：録画のスクリプトを記事用に写す（20261029_29）

上の3つ（部品の全文・差分・コマンド）は、記事に全量で載せるために、録画のデータの横に写してもらったものです。

```markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 20
---
# 依頼：LP 用の録画に使ったスクリプトを、データと対のコードとして写す（2026-10-29）

LOG404（LP に丸テーブルの前後の動画を並べた記事）に全量で載せるため、依頼26・28 で足した録画のコードを、録画のデータの横に写してほしい。ファイルを写すだけで、プロジェクトは何も変えない（check-in もしない）。

## やること

1. 次の2つの今の版（cs:43）を、`/Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261029_lpdemo2_cs43_fam_p0_r2_s0_passon_sbret/scripts/` に写す（フォルダがなければ作る。同じ名前のファイルがあれば止める）
   - `Assets/ServiceSim/Recording/SimTimeRecorder.cs`
   - `Assets/ServiceSim/Editor/RecordBuild.cs`
2. 同じフォルダに、cs:40→cs:41・cs:41→cs:42・cs:42→cs:43 の差分を、それぞれテキストで書き出す（`diff_cs40_cs41.txt` など。cm diff の出力そのまま）
3. 同じフォルダに、録画の実行ファイルを作って回したときのコマンド（依頼26・28 で実際に打ったもの。起動の引数を含む）を `commands.txt` に書く。覚えていない所は、作業記録（logs/）から拾い、拾えなかった所はそう書く

## 変えないもの

- ServiceSim のファイル・設定・保留（最後に保留 0 を確かめる）
- KIOXIA の今までの出力・録画・台帳

## 報告

依頼箱の結果に、写したファイルの名前と大きさ、保留 0 だったかを書く。
```

### 依頼箱の結果：20261029_29_録画のスクリプトを記事用に写す_20261029_2253.md

依頼26・28 のコマンドは作業の記録（logs/）に残っておらず、Claude Code の会話の記録から写したものです。人が手で打ったコマンドがあったかどうかは分かりません。

```markdown
# 結果：20261029_29_録画のスクリプトを記事用に写す

- 状態：完了
- 始めた時刻：2026-10-29 22:53
- 終わった時刻：2026-10-29 23:01（8分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume d77f72e6-1981-4915-b64a-349cb3d9685f`

## Claude Code の報告

**やったこと**
- `scripts/` フォルダを新しく作り、その中に6つのファイルを書きました。作る前にフォルダが無いことを確かめたので、同じ名前のファイルとぶつかることはありませんでした。置き場所は `/Volumes/KIOXIA/r2r2r-data/service_sim/unity/20261029_lpdemo2_cs43_fam_p0_r2_s0_passon_sbret/scripts/` です。
- 2つのスクリプトは、cs:43 の版を `cm cat` で取り出して写しました。作業フォルダにあるファイルと1字も違わないことを cmp で確かめています。RecordBuild.cs は cs:41 から変わっていません。
- 差分のファイルについて：cm diff を cs と cs の間で回すと、変わったファイルの名前の一覧しか出ません（cs:40→41 は4件、cs:41→42 と cs:42→43 は SimTimeRecorder.cs の1件ずつ）。そこで各ファイルの最初に、その一覧をそのまま載せました。その後ろに「補足」と見出しを付けて、中身の違いを足しています。中身の違いは、各ファイルを `cm cat` で取り出し、`diff -u` で比べたものです。cs:40→41 は、足した .meta の2つも含みます。
- commands.txt：依頼26・28 のコマンドは、logs/ には残っていませんでした（r2r2r-scripts/logs/ にある 20261029 の2本は、依頼25・27 のものです）。代わりに、2つの依頼のときの Claude Code の作業の記録（セッションの記録）から、実際に打ったコマンドをそのまま写しました。中身は次のとおりです。
  - 試し、実行ファイルを作るところ、回すところ（起動の引数はすべて入っています）
  - check-in、直す前の版の14ファイルを写す・戻す（cm undo）、screens の書き戻し
  - `$(date +%H%M)` で決まった時刻：出力のファイル名や build_id から分かるものは、横に書き添えました

**結果**
- ServiceSim は cs:43 のままで、保留は 0 でした（最後に `cm status --machinereadable` で見て、STATUS の行だけでした）。
- KIOXIA でこれまでに出した出力・録画・台帳には、触っていません。
- 写したファイル（大きさはバイト）

| ファイル | 大きさ |
|---|---|
| SimTimeRecorder.cs | 18,616 |
| RecordBuild.cs | 1,788 |
| diff_cs40_cs41.txt | 16,160 |
| diff_cs41_cs42.txt | 8,878 |
| diff_cs42_cs43.txt | 2,675 |
| commands.txt | 22,724 |

- 拾えなかったこと：記録に残っているのは Claude Code が打ったコマンドだけです。人が手で打ったものがあったかどうかは分かりません。commands.txt にもそう書きました。
- 1つ気づいたこと：依頼26 の試しの出力（rectest の2フォルダ）は、今も KIOXIA に残っています。依頼26 の報告にあるとおり、消す許可が出なかったためです。commands.txt の注にも書き、触っていません。
- まちがいが1つありました。最初に差分を取ろうとしたとき、私が書いたシェルの書き方が誤っていて、`cm diff` が正しくない引数で動き、止まったままになりました。その処理は止めました。止めた後も保留は 0 のままで、何も変わっていないことを確かめています。

**変えたファイル**
- 新しく作ったのは、上の `scripts/` の6ファイルだけです。ServiceSim は何も変えておらず、check-in もしていません。

**決めてほしいこと**
1. 差分のファイルの形について。cm diff をそのまま出すとファイル名の一覧だけになるため、補足として `diff -u` の中身を足しました。記事に載せるのが cm diff の出力そのままだけでよいなら、補足の部分を外してください。
```


## 作業ログ ― LP の「どこで止まるか」に、前後の動画を並べた

### LP に入れた差分（3つの commit をまとめたもの）

数字の欄に「丸テーブルの間で、ロボットが動けなくなる 8→0回」の行を足し、4つの視点の動画の下に、直す前と直した後を並べる枠を足しました。PC では2本を左右に、幅640px以下では上下に並べます。動画は1本約0.12MBなので、LP のほかの動画と同じ場所（public/site/srl/）に置きました。

```
5c987f4 2026-10-29 21:17 LP（配膳ロボット）：どこで止まるかに、②の丸テーブルを直す前・直した後の動画を並べる下書き（動画は撮り直し待ち・未公開）
 src/pages/serving-robot-one-less-staff.astro | 35 +++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)
b84844e 2026-10-29 22:32 LP（配膳ロボット）：丸テーブルの前後の動画（影を短くして撮り直した版）を入れ、PCで文字の下に並ぶよう順番を直す
 public/site/srl/round-after.mp4              | Bin 0 -> 124815 bytes
 public/site/srl/round-after.webp             | Bin 0 -> 12882 bytes
 public/site/srl/round-before.mp4             | Bin 0 -> 127990 bytes
 public/site/srl/round-before.webp            | Bin 0 -> 12796 bytes
 src/pages/serving-robot-one-less-staff.astro |   4 ++--
 5 files changed, 2 insertions(+), 2 deletions(-)
2f21f1e 2026-10-29 22:55 LP（配膳ロボット）：丸テーブルの前後の動画を、2本とも読み込めてから同時に流し、ずれたら合わせ直す
 src/pages/serving-robot-one-less-staff.astro | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)
```

```diff
diff --git a/src/pages/serving-robot-one-less-staff.astro b/src/pages/serving-robot-one-less-staff.astro
index ec5b9ce..0158afa 100644
--- a/src/pages/serving-robot-one-less-staff.astro
+++ b/src/pages/serving-robot-one-less-staff.astro
@@ -151,12 +151,28 @@ const MENU = [
           <div class="kn-nums">
             <div><span>細い通路の入口で、ロボットどうしが鉢合わせ</span><b>79<i>→</i><em>0</em><small>回</small></b></div>
             <div><span>レジのまわりでロボットが止まる</span><b>112<i>→</i><em>31</em><small>回</small></b></div>
+            <div><span>丸テーブルの間で、ロボットが動けなくなる</span><b>8<i>→</i><em>0</em><small>回</small></b></div>
           </div>
         </div>
         <figure class="clip clip-4">
           <video class="loop" src="/site/srl/four-views.mp4" poster="/site/srl/four-views.webp" muted loop playsinline preload="none" aria-label="3Dの店舗を、真上・正面・横・斜めの4つの視点で同時に映した動画。キッチンのモニターに、ロボットとスタッフがいま何をしているかが出ている"></video>
           <figcaption>同じ店舗を、真上・正面・横・斜めの4つの視点で同時に見る。左上の枠（キッチンのモニター）に、ロボットとスタッフがいま何をしているか、止まった回数が出ます。</figcaption>
         </figure>
+        <!-- 直す前・直した後（LOG402 の②・丸テーブル T06 を右へ1.0m）。同じ客・同じ時刻で走らせた Unity の録画を左右に並べ、同時に流す（2026-10-29）
+             元の録画は KIOXIA の service_sim/unity/20261029_lpdemo2*_cs43_fam_p0_r2_s0_passon_sbret/recordings/（録画のときだけ光を75°に上げて影を短くした）。経過15:30〜18:00を12倍速に切り出した -->
+        <figure class="ba" id="baPair">
+          <div class="ba-pair">
+            <div class="ba-v">
+              <video src="/site/srl/round-before.mp4" poster="/site/srl/round-before.webp" muted playsinline preload="none" aria-label="直す前。丸テーブルの間で白いロボット2台が詰まり、青いスタッフがまわりを回っている"></video>
+              <p class="ba-k"><b class="ba-tag">直す前</b>丸テーブルの間 1.1m</p>
+            </div>
+            <div class="ba-v">
+              <video src="/site/srl/round-after.mp4" poster="/site/srl/round-after.webp" muted playsinline preload="none" aria-label="直した後。同じ時刻に、ロボットは丸テーブルの間を通り抜けていく"></video>
+              <p class="ba-k"><b class="ba-tag after">直した後</b>右の丸テーブルを1.0m右へ／間 2.1m</p>
+            </div>
+          </div>
+          <figcaption>昔のファミレスの間取りをもとにした19卓の店舗。同じ客・同じ注文・同じ時刻で2回走らせ、違うのは右の丸テーブルの位置だけです（ロボット2台・真上から・12倍速）。右下は、走らせ始めてからの経過時間です。</figcaption>
+        </figure>
       </div>
       <div class="kn">
         <div class="kn-text">
@@ -326,10 +342,26 @@ const MENU = [
     document.addEventListener('visibilitychange', function(){ if (document.hidden) { clearTimeout(timer); vids(scs[si], false); } else show(si); });
   }
   var loops = document.querySelectorAll('.srl video.loop');
-  if (still) { loops.forEach(function(v){ v.controls = true; }); }
+  if (still) { loops.forEach(function(v){ v.controls = true; }); document.querySelectorAll('#baPair video').forEach(function(v){ v.controls = true; }); }
   else if ('IntersectionObserver' in window) {
     var io = new IntersectionObserver(function(es){ es.forEach(function(e){ var v = e.target; if (e.isIntersecting) { v.play().catch(function(){}); } else { v.pause(); } }); }, { threshold: .35 });
     loops.forEach(function(v){ io.observe(v); });
+    // 直す前・直した後の2本は、見えたら頭から同時に流し、どちらかが終わったら2本とも頭に戻す
+    var ba = document.getElementById('baPair');
+    if (ba) {
+      var bv = [].slice.call(ba.querySelectorAll('video'));
+      // 2本とも再生できる状態になってから頭をそろえて流す（片方の読み込みが遅いと、先に流れた方とずれるため）
+      var ready = function(){ return bv.every(function(v){ return v.readyState >= 3; }); };
+      var restart = function(){
+        if (!ready()) { bv.forEach(function(v){ v.addEventListener('canplay', function once(){ v.removeEventListener('canplay', once); if (ready()) restart(); }); }); return; }
+        bv.forEach(function(v){ try { v.currentTime = 0; v.play().catch(function(){}); } catch(e){} });
+      };
+      bv.forEach(function(v){ v.addEventListener('ended', restart); });
+      // 流れている間も、右を左の時刻に合わせ直す（0.15秒以上ずれたら）
+      bv[0].addEventListener('timeupdate', function(){ var d = bv[1].currentTime - bv[0].currentTime; if (!bv[1].paused && Math.abs(d) > .15 && !bv[0].ended) { try { bv[1].currentTime = bv[0].currentTime; } catch(e){} } });
+      var seen = false;
+      new IntersectionObserver(function(es){ es.forEach(function(e){ if (e.isIntersecting) { if (!seen) { seen = true; bv.forEach(function(v){ v.preload = 'auto'; }); restart(); } else { bv.forEach(function(v){ v.play().catch(function(){}); }); } } else { bv.forEach(function(v){ v.pause(); }); } }); }, { threshold: .35 }).observe(ba);
+    }
   }
 
   // ---- 02 ①：店が回る最少のスタッフ ----
@@ -530,6 +562,14 @@ const MENU = [
 .srl .srow em.same{background:#f0f0f0;color:var(--muted)}
 .srl .srow em.cut{background:var(--ink);color:#fff}
 .srl .srow em.plus{background:transparent;color:var(--muted);border:1px solid var(--rule)}
+.srl .ba{grid-column:1/-1;order:3;display:grid;gap:12px;margin:0}
+.srl .ba-pair{display:grid;grid-template-columns:1fr 1fr;gap:16px}
+.srl .ba-v{display:grid;gap:10px;min-width:0}
+.srl .ba video{display:block;width:100%;max-width:100%;height:auto;aspect-ratio:992/864;background:#e8e2d6;border-radius:var(--r-m)}
+.srl .ba-k{margin:0;display:flex;align-items:baseline;flex-wrap:wrap;gap:10px;font-size:.84rem;font-weight:700}
+.srl .ba-tag{display:inline-block;padding:3px 10px;border-radius:4px;font-size:.8rem;background:#f0f0f0;color:var(--muted)}
+.srl .ba-tag.after{background:var(--ink);color:#fff}
+@media (max-width:640px){.srl .ba-pair{grid-template-columns:1fr}}
 .srl .clip{display:grid;gap:10px}
 .srl .clip-4{max-width:480px;justify-self:center;width:100%}
 .srl .clip-4 video{aspect-ratio:694/720!important}
```

### PC で、前後の動画が文字の上に割り込んでいた

最初の下書きをビルドして PC の幅で見ると、前後の動画が「どこで止まるか」の文字の上に入り、文字が動画の下へ押し出されていました。この節は、文字の列を `order:2` で右に回す作りだったので、新しく足した枠（`order` なし）が先に並んでいたのが原因です。枠に `order:3` を付け、文字の下に並ぶようにしました。

### 公開した直後、2本が0.67秒ずれていた

公開したページで撮った画面では、左の時刻が17:12、右が17:04で、店の時刻で8秒（動画で約0.67秒）ずれていました。最初の作りは、画面に入ったら2本に「頭から再生」を出すだけで、片方の読み込みが遅いと、先に読めた方だけが流れ始めていました。2本とも再生できる状態になってから頭をそろえて流し、流れている間も右を左の時刻に合わせ直す（0.15秒以上ずれたら）作りに直しました。

- ねらい：同じ時刻の比べであることを、ずれのない2本で見せる
- 変わったこと：2本とも読み込めてから同時に流す。流れている間に0.15秒以上ずれたら右を左に合わせる
- 見込める効果：右の動画だけ2.5秒遅れて届く条件で試すと、ずれは0.01秒だった（測った値。下の sync.js の出力）

### 片方の動画を遅らせて、ずれを測った sync.js

確かめには [Playwright](https://playwright.dev/)（ブラウザを自動で動かす道具）を使い、ビルドしたページを手元で開いて、右の動画の読み込みだけを2.5秒遅らせました。

```javascript
const { chromium } = require('playwright');
(async () => {
  const b = await chromium.launch();
  const p = await b.newPage({ viewport: { width: 1280, height: 900 } });
  await p.route('**/round-after.mp4', async r => { await new Promise(x=>setTimeout(x,2500)); await r.continue(); });
  await p.goto('http://localhost:4322/serving-robot-one-less-staff.html?p2m-notrack=1');
  await p.evaluate(() => document.getElementById('baPair').scrollIntoView({block:'center'}));
  for (const t of [1500, 5000, 9000, 15000]) { await p.waitForTimeout(t === 1500 ? 1500 : 3500);
    console.log(await p.evaluate(() => [...document.querySelectorAll('#baPair video')].map(v => v.currentTime.toFixed(2)+(v.paused?'p':'')).join(' / '))); }
  await b.close();
})();
```

### sync.js の出力（右の動画を2.5秒遅らせたとき）

```
0.00p / 0.00p
2.44 / 2.43
5.94 / 5.93
9.45 / 9.44
```

### 公開したページを PC とスマホの幅で撮った shot2.js

```javascript
const { chromium } = require('playwright');
(async () => {
  const b = await chromium.launch();
  for (const [w,h,name] of [[1280,1700,'03_lp_pc'],[390,2400,'04_lp_phone']]) {
    const p = await b.newPage({ viewport: { width: w, height: h }, deviceScaleFactor: 2 });
    await p.goto('https://pixelstomotors.com/serving-robot-one-less-staff?p2m-notrack=1', { waitUntil: 'networkidle' });
    await p.addStyleTag({content:'header,.site-header,[class*="header"]{position:static!important} body::before{display:none!important}'});
    await p.evaluate(() => { document.querySelectorAll('video').forEach(v=>{v.preload='auto';}); });
    await p.evaluate(() => document.querySelector('.kn.rev').scrollIntoView({block:'start'}));
    await p.waitForTimeout(9000);
    const el = await p.$('.kn.rev');
    await el.screenshot({ path: `${process.argv[2]}/img/LOG404_${name}.png` });
  }
  await b.close();
})();
```

【画像】LOG404_03_lp_pc.png 公開した LP の「どこで止まるか」を PC の幅で見たところ。上に4つの視点の動画と数字の欄（79→0回・112→31回・8→0回）、その下に直す前と直した後の動画が左右に並ぶ

【画像】LOG404_04_lp_phone.png 同じ節をスマホの幅（390px）で見たところ。数字の欄のあとに4つの視点の動画、その下に直す前と直した後が上下に並ぶ

【画像】LOG404_01_round_tables_before_after.png 店の時刻1500秒（経過25:00）の、直す前（左）と直した後（右）の同じコマ。左は丸テーブルの間で白いロボット2台が止まったまま、右は丸テーブルのまわりが空いている


## 作業ログ ― 次に別の店で前後を見せるときも、同じ撮り方が使える

録画のときだけ光を上げる作りと、店の時刻に合わせて撮る作りは、Unity のプロジェクトに残っています（cs:43）。夜に回すシミュレーションの見た目と動きは変えていません。別の店で店の形や回し方を直したときも、同じ種・同じ時刻で直す前と後を撮れば、同じ形で LP や記事に並べられます。

| 残したもの | 場所 |
|---|---|
| 寄りの録画（75°・直す前と直した後） | KIOXIA の service_sim/unity/20261029_lpdemo2*_cs43_fam_p0_r2_s0_passon_sbret/recordings/ |
| 最初の録画（52°・寄りと全体） | KIOXIA の service_sim/unity/20261029_lpdemo*_cs41_fam_p0_r2_s0_passon_sbret/recordings/ |
| 光の角度を比べた静止画 | KIOXIA の service_sim/unity/20261029_lpdemo2still_cs43_fam_p0_r2_s0_passon_sbret/recordings/ |
| 録画の部品の全文と差分 | KIOXIA の service_sim/unity/20261029_lpdemo2_cs43_fam_p0_r2_s0_passon_sbret/scripts/ |
| LP に置いたループ | p2m-site の public/site/srl/round-before.mp4・round-after.mp4（と webp） |

台帳は、672行（追記だけ）になりました。
