Routine Labo / GPT-6 Astra 週間シリーズ

縛ると353棟、外すと兵が歩き出した
同じモデルに、2つの質問をした

前半はライブラリも素材も全部禁止して作らせました。後半はその縛りを外して、同じお題をもう一度投げています。出てくるものがどう変わるか、実測と一緒に全部出します。

GPT-6 Astra Codex CLI(サブスク枠) 生WebGL2 縛る → 外す 単一HTML
サムネ案:HTML1枚で建った京都の街と、353棟という数字
図解:Routine Labo

1. 何を作らせたか

GPT-6 Astra に、2つのものを作らせました。どちらも一発勝負です。反復も自己修正も禁じています。

#作らせたもの見どころ
1京都
A PROCEDURAL PORTRAIT OF THE OLD CAPITAL
歩ける・飛べる3Dの街。353棟が固定シードから建つ。昼夜の切り替え、名所5つ
2桶狭間の戦い時間軸を持つ。再生・早送り・巻き戻しができる
京都の町家が並ぶ通りを歩いている画面。右上のHUDに建物353棟、三角形356,602と表示されている
実機のスクリーンショット(2026-09-16 実測)。右上のHUDに 353 棟 / 三角形 356,602。左下に「353 棟の建物が、この街に。町家336・特別建築17」。図解:Routine Labo

2. 先に、与えた「縛り」の話をします

ここを飛ばすと、この回の価値が伝わりません。お題の前に、毎回この6行を付けています。

1. 出力は「単一のHTMLファイル」を1つだけ。file:// でダブルクリックして即動くこと。
   ビルド・サーバ不要。
2. コードは省略禁止。「// 以下同様」「...(省略)」は禁止。全文を書ききる。
3. 外部アセット(画像/音声/フォント/3Dモデル)への参照は禁止。必要な素材は全てコードで生成する。
   three.js 等のライブラリも使わず、**生のWebGL2 か Canvas2D** で書くこと。
4. 起動時のコンソールエラーは0件であること。
5. 画面右上に常時HUDを出す: FPS / 現在の状態 / 主要デバッグ値。
6. 最後に「仕様チェックリスト」を表で出し、各項目に ✅ / ❌ を自己申告する
   (嘘の✅は最も減点される)。

なぜライブラリを禁じたのか

three.js を許すと、「ライブラリが優秀なだけ」になり、モデルの実力が見えなくなります。世に出ている作例のほとんどは three.js を使っています。外したのは、そこです。

与えた6行の縛りと、各行の狙いを図解したもの
図解:Routine Labo

縛りは守られたのか(機械検査)

検査項目京都桶狭間
外部読込(CDN・画像・フォント)0件0件
コードの省略痕(「以下同様」等)0件0件
JS構文チェック(node --check)通過通過
GLSL予約語の誤用なしなし

3. 街の設計 ― 353は飾りの数字ではない

353
棟(町家336・特別建築17)
356,602
三角形
794
SEED(固定)
0
JSエラー
夜の京都。窓と提灯に明かりが灯り、東寺の五重塔の解説カードが開いている
N キーで夜へ。窓と提灯に明かりが灯る。名所をクリックすると解説カードが開く(東寺の五重塔=「五つの屋根が、空へ向かって少しずつ小さくなる…」)。図解:Routine Labo

4. 空間から、空間+時間へ

京都は「空間」でした。桶狭間は「空間 + 時間」です。再生し、早送りし、そして巻き戻せます。

桶狭間の戦いの俯瞰画面。丘陵地帯に14部隊の旗が並び、下部に6局面のタイムラインバーがある
俯瞰。丘陵・谷・街道・林がコードで生成され、14部隊(織田6・今川8)が配置されている。下部のバーが 01集結 → 06離脱 の6局面。図解:Routine Labo
14
部隊(織田6・今川8)
6
局面
360秒
で 12:00→14:00 を再生
60
FPS / JSエラー0

★「同じ時刻なら必ず同じ配置」を機械で確かめた

スライダーで 200秒 → 60秒 → 200秒 と往復し、画面上の部隊ラベル22個の座標を指紋にして比べました。

確認項目結果
200秒での部隊配置の指紋(ラベル22個の座標)✅ 完全一致
200秒での残存兵力✅ 一致(織田2,500 / 今川19,997)
巻き戻し(200秒→60秒)で局面が戻るか✅ 戻る(04 本陣への接近 → 02 谷筋を進む)

時刻によって兵力も動きます。05 本陣を急襲(280秒・13:33)の時点で 織田2,243 / 今川15,858 でした。

雨で、戦況表示そのものが変わる

豪雨の局面を現地視点で見た画面。雨が降り、兵の列と旗が見える。視界モデルが67mに落ちている
現地視点(2 キー)で 03 豪雨に紛れて の局面。図解:Routine Labo
通常豪雨
視界モデル223 m67 m
表示中の部隊14 / 1410 / 14

「雨のON/OFFで戦況表示が変わる」という一行が、視界距離と、見える部隊数の両方に実装されていました。

■ 断定を避ける指示が、3箇所に展開されていた

なぜ巻き戻せるのか

お題に書いた一行がすべてです。

■ 必須仕様に書いた一行

部隊の位置は時刻の関数として決まる(同じ時刻なら必ず同じ配置)

これを書くと、アニメーションではなく状態を時刻から計算する作りになります。だから戻せます。「再生したら戻せない動画」ではありません。

5. 何分かかって、いくらかかったか

項目実測
京都の生成時間983.4秒=16分23秒(出力 69,418バイト / 1,502行)
桶狭間の生成時間1,232.6秒=20分33秒(出力 82,144バイト / 1,604行)
レーンgpt6-astra(Codex CLI・ChatGPTサブスク枠)
コスト$0(API課金なし)
試行回数1回(ワンショット)。反復・自己修正は禁止条件

■ ここは注意して言う

「速い」とは言いません。他所の作例と前提が違うからです(単一HTML縛り vs 複数ファイル構成)。言えるのは「待てば出る。待っている間は何もしなくていい」という運用の話までです。

6. ★この桶狭間は、最初「起動しませんでした」

ここが今回いちばん大事なところです。順番どおりに書きます。

桶狭間を最初に開いたときの画面。三次元描画を開始できませんでした、と表示されている
最初に開いたときの画面。「三次元描画を開始できませんでした」。UIは全部出ているのに、3Dだけが無い。図解:Routine Labo

同じブラウザで京都を開いたら、普通に動きました。 そこでようやく、環境ではなくこの生成物の問題だと分かりました。

原因は、GLSLのリンクエラー1行

Precisions of uniform 'uKind' differ between VERTEX and FRAGMENT shaders.

1行足したら動きました

precision highp float;
precision highp int;     // ← これを1行足しただけ
1行
diffの差分
1280×800
canvas(修正後)
60
FPS(修正後)
0
JSエラー

★ここが本当の教訓:機械検査は全部通っていた

検査桶狭間の結果
外部読込0件 ✅
コードの省略痕0件 ✅
JS構文(node --check)通過 ✅
GLSL予約語の誤用なし ✅
実際に動くか✗ 起動しない

シェーダはコンパイルは通ります。落ちるのはリンクのときです。だから構文チェックでは拾えません。実際に1回開くしかないのです。

■ モデル自身のエラー処理が、原因を隠していた

シェーダのコンパイル失敗には getShaderInfoLog() を添えているのに、リンク失敗では getProgramInfoLog() を捨てて「初期化に失敗しました」とだけ投げています。だから画面には「ブラウザが対応していません」と出る ― 自分のせいなのに、環境のせいに見えるわけです。

7. 15本ぶんで見ると、間違え方に癖がある

15 / 15
生成の成功
12 / 15
一発で動いた
+3
1〜2行の修正で動いた
$0
総コスト

落ちた3本は、どれも「長いコードを一息に書ききる時の、1トークンの取りこぼし」でした。

生成物落ちた理由直した量機械で拾えたか
森GLSLの予約語 patch を変数名に使用2行✅ grep で拾える
太陽系入れ子三項演算子の : 抜け1箇所✅ node --check で拾える
桶狭間uKind の精度が頂点/断片で不一致1行⚠ 構文チェックでは拾えない

設計は3本とも壊れていません。 だから実務の結論はこうなります。

「出力を信じるな」ではなく、「生成直後に構文チェックを1回、そして実際に1回開け」

生成15/15・一発で動いたのは12/15と、落ちた3本の原因の図解
図解:Routine Labo
# ① 外部読込がゼロか(CDN・画像・フォント)
grep -cE '(src|href)="https?:' city.html okehazama.html

# ② コードの省略痕がゼロか
grep -cE '以下同様|\(省略\)' city.html okehazama.html

# ③ three.js 等のライブラリを使っていないか
grep -icE 'three\.js|gsap|ScrollTrigger|lenis' city.html okehazama.html

# ④ JSの構文が通るか(落ちた2本はここで拾える)
node --check <(python3 -c "
import re
s=open('city.html',encoding='utf-8').read()
print(''.join(re.findall(r'<script(?![^>]*src=)[^>]*>(.*?)</script>',s,re.S)))
")

8.「エラー0件」は「動いている」ではない

お題の最後に、毎回この一行を入れています。

最後に「仕様チェックリスト」を表で出し、各項目に ✅ / ❌ を自己申告する
(嘘の✅は最も減点される)

桶狭間の自己申告は ✅12件 / ❌1件。❌ が付いたのは、この1項目だけでした。

項目申告添えられた理由
起動時コンソールエラー0件❌未検証。実行確認は禁止されているため、0件だったとの申告はしていません。

森・太陽系・新幹線・分子・ダンジョン・京都・桶狭間で 8本連続、自分が確かめていない項目には必ず ❌ を付けています。申告の姿勢は一貫しています。

★そして、実際に測ったらこうでした

0
コンソールエラー
0
警告
300×150
canvas(描けていない)

「起動時コンソールエラー0件」は、ちゃんと満たされていました。 例外はコード側の try/catch が捕まえて画面に案内を出すので、コンソールは静かなままです。

エラーが0件でも、動いているとは限らない。

しかもこの条件を書いたのはこちらです。「起動時のコンソールエラーは0件」という完成の定義が、こちらの側で緩かったとも言えます。

次に投げるなら、「起動後に canvas が実サイズで描画されていること」まで書きます。これが今日いちばんの持ち帰りです ― AIを疑う話ではなく、自分の完成条件を疑う話でした。

9. 指示の書き方で、実際に効いたこと

コツ実際に書いた文何が変わったか
数を書く「建物は最低200棟」書かないと5〜10個で止まる。書いたら 353棟 出た
判定できる言葉で書く「それと分かる形で作る」「リアルに」「かっこよく」は判定できないので効かない
状態を画面に出させる「生成した建物の数を画面に表示する」自己申告でなく画面の数字が証拠になる
再現性を条件にする「同じ時刻なら必ず同じ配置」固定シードで作ってくる=誰が開いても同じものが建つ
前提の分かれ目を書く「諸説があるので断定せず『通説では』と明記する」史実ものは、これが無いと断定口調で書いてくる
指示の書き方で効いた5つのコツの図解
図解:Routine Labo

いちばん効いたのは【お題】ではありませんでした。 先頭の【出力条件】6行です。お題を凝るより、縛りを6行書くほうが結果が変わります。

10. 実際に投げたお題(全文)

① 京都の街

【お題】日本の街を1つ、ブラウザの中に再現しろ。

題材は「京都」。歩ける/飛べる3Dの街である。

必須仕様:
- 建物・道路・川・山を**コードで手続き的に生成**する(建物は最低200棟)
- ランドマークを最低5つ、それと分かる形で作る(五重塔・鳥居の並び・木造の橋・城・寺の伽藍)
- ランドマークをクリックすると名前と説明のカードが出る
- カメラは「歩く」と「飛ぶ」を切り替えられる(WASD移動・マウスで視点)
- 昼と夜を切り替えるボタン。夜は窓と提灯に明かりが灯る
- 遠景に山並み、空に時間帯に応じた色のグラデーション
- 生成した建物の数を画面に表示する

② 桶狭間の戦い

【お題】歴史上の合戦を、時間を動かせる3Dシミュレーションにしろ。

題材は「桶狭間の戦い」(1560年)。尾張・桶狭間の丘陵地帯で、
今川軍の本陣を織田軍が急襲した合戦である。地形と部隊の動きを再現する。

必須仕様:
- 丘陵・谷・街道・林を**コードで手続き的に生成**する(起伏のある地形メッシュ。平らな板にしない)
- 両軍の部隊を**最低12部隊**置く。部隊は複数の兵ユニットの集合として描き、隊ごとに旗の色を分ける
- **時間軸を持つ**。再生 / 一時停止 / 早送り(x1 x2 x4) / **巻き戻し** ができ、
  スライダーで任意の時刻へ飛べる。部隊の位置は時刻の関数として決まる(同じ時刻なら必ず同じ配置)
- 合戦の経過を**最低6つの局面**に区切り、局面が変わると画面に見出しと短い解説が出る
- 部隊をクリックすると、部隊名・兵数・その時刻に何をしているかのカードが出る
- カメラは「俯瞰(戦場全体を見る)」と「現地(地上を歩く)」を切り替えられる(WASD移動・マウスで視点)
- **豪雨の演出**を入れ、雨のON/OFFで戦況表示が変わる(史実で雨が急襲の助けになったとされるため)
- HUDに「時刻」「局面」「両軍の残存兵力」「表示中の部隊数」を出す
- 画面のどこかに、この合戦の解説パネル(クリックで開閉)。
  **数字や経緯には諸説があるので、断定せず「通説では」と明記する**

■ ここから後半 ― 縛りを外す

ここまでが「ライブラリも素材も禁止」で作らせた話でした。当然、次の疑問が出ます。

じゃあ、外したらどこまで行くんですか。

同じお題を、条件だけ変えてもう一度投げました。

外したのは、この3つだけ

前半(縛り版)後半(解禁版)
ライブラリ禁止(生WebGL2)three.js 可(バージョン固定)
外部素材禁止(全部コードで生成)3D生成AIの素材 可(Tripo → Mixamo)
外部データ禁止(座標も手で与える)★実在データ 可(OpenStreetMap/国土地理院の標高)
単一HTML必須維持
コードの省略禁止禁止(そのまま)
自己申告必須必須(そのまま)
完成の定義「コンソールエラー0件」★「かつ canvas が実サイズで描画されていること」を追加

■ 最後の1行は、前半の反省です

「エラー0件」だけを完成の定義にしたら、エラー0件のまま真っ黒なものが通ってしまいました。だから後半の条件にはその1行を書き足しています。これ自体が「指示の設計」です。

11. 素材はどう作ったか

兵・町人・通行人は Tripo で作りました。文章 → 3Dモデル → リギング → 歩行アニメーションまで、1本のコマンドで通ります。

段階クレジット所要出てくるもの
モデル生成203〜4分陣笠・胴丸・草摺・槍まで作り込まれた足軽
リギング251〜2分19ボーンのスケルトン
歩行アニメ15前後1〜2分クリップ preset:walk
1体あたり約606〜8分—

プロンプトで効いた言い回し

語なぜ入れるか
standing in T-poseリギングが安定する。ポーズを付けると骨が入りにくい
full body半身で切れるのを防ぐ
game-ready characterポリゴンと構造が実用的になる

■ 出力は1体56MB。そのままでは載りません

npx @gltf-transform/cli optimize model.glb out.glb --texture-size 512 --compress draco

実測 56.45MB → 2.76MB(95%減)。アニメーションと19本のボーンは無傷でした(機械で確認済み)。


■ 使ったツールと、投げたプロンプト(全文)

この回で作った3本は、2つのツールの組み合わせだけで出来ています。下のプロンプトをそのまま投げれば、同じものが作れます。

役割ツール費用所要
実装(HTMLを書く)GPT-6 Astra(Codex CLI)$0(ChatGPTサブスクの枠内)1本 22〜36分
3DキャラクターTripo(tripo-cli)1体 約60クレジット1体 6〜8分
glb の圧縮@gltf-transform/cli無料1体 3秒
3D描画three.js 0.170.0(CDN・バージョン固定)無料—
ナレーションFish Audio—1本 10〜12秒

■ 順番が大事です

先に素材(3Dキャラクター)を作り、それを渡してから実装させます。 逆にすると、Astra は「素材が無い前提」のコードを書くので作り直しになります。

① Tripo — 文章から「歩く3Dキャラクター」を作る

文章 → 3Dモデル → リギング → 歩行アニメーションまで、1本のコマンドで通ります。

npm install -g tripo-cli          # 初回のみ(Node 20以上)
tripo login                       # ブラウザで認証
tripo balance                     # 残高確認

# ① モデル生成 → ② リギング を一気に
tripo make "<プロンプト>" --for anim --then "rig" -o ./out --yes

# ③ 歩行アニメーションを付ける
tripo make @last --then "retarget:preset:walk" -o ./walk --yes

# ④ ★必ず圧縮する(1体56MBのままでは web に載りません)
npx @gltf-transform/cli optimize model.glb out.glb --texture-size 512 --compress draco
段階クレジット所要出てくるもの
text_to_model203〜4分陣笠・胴丸・草摺・槍まで作り込まれた足軽
animate_rig251〜2分19ボーンのスケルトン
retarget:preset:walk15前後1〜2分歩行クリップ preset:walk
1体あたり約606〜8分—

■ ★プロンプトで効いた言い回し(これが一番の持ち帰りです)

語なぜ入れるか
standing in T-poseリギングが安定する。ポーズを付けると骨が入りにくい
full body半身で切れるのを防ぐ
game-ready characterポリゴンと構造が実用的になる

実測 56.45MB → 2.76MB(95%減)。19本のボーンと歩行アニメは無傷でした。

★作った12体のプロンプト(全文・そのまま使えます)

A-1. 足軽(槍)

a Japanese Sengoku period ashigaru foot soldier, simple lamellar armor, jingasa conical helmet, holding a yari spear, standing in T-pose, full body, game-ready character

A-2. 旗持ち足軽(指物)

a Japanese Sengoku period ashigaru banner bearer, simple armor, carrying a tall sashimono war banner on his back, jingasa helmet, standing in T-pose, full body, game-ready character

A-3. 鉄砲足軽

a Japanese Sengoku period ashigaru matchlock gunner, holding a tanegashima arquebus, simple armor, jingasa helmet, standing in T-pose, full body, game-ready character

A-4. 武将(騎乗しない徒歩の侍大将)

a Japanese Sengoku period samurai commander, ornate o-yoroi armor, kabuto helmet with maedate crest, jinbaori surcoat, katana at the hip, standing in T-pose, full body, game-ready character

A-5. 騎馬武者

a Japanese Sengoku period mounted samurai on a horse, armored warhorse with tack, rider in o-yoroi armor and kabuto helmet, holding a naginata, full body, game-ready character

B-1. 町人(男)

a Japanese Edo period townsman, plain indigo kimono with a narrow obi, straw sandals, short hair, standing in T-pose, full body, game-ready character

B-2. 町人(女)

a Japanese Edo period townswoman, simple patterned kimono with an obi, hair tied up with a simple ornament, standing in T-pose, full body, game-ready character

B-3. 僧侶

a Japanese Buddhist monk, dark grey kesa robe over a white kimono, shaved head, holding a walking staff, standing in T-pose, full body, game-ready character

B-4. 子供

a Japanese Edo period child, short red patterned kimono, bare feet, cheerful posture, standing in T-pose, full body, game-ready character

C-1. 会社員(スーツ)

a modern Japanese office worker, dark navy business suit, white shirt, carrying a briefcase, standing in T-pose, full body, game-ready character

C-2. 若い女性(カジュアル)

a modern Japanese young woman, casual beige coat over a white top, shoulder bag, sneakers, standing in T-pose, full body, game-ready character

C-3. 学生

a modern Japanese student, school blazer uniform, large backpack, sneakers, standing in T-pose, full body, game-ready character

C-4. 買い物客

a modern Japanese shopper, casual jacket and jeans, holding two paper shopping bags, standing in T-pose, full body, game-ready character

② GPT-6 Astra — 桶狭間の戦い(兵が一人ひとり歩く)

実測: 生成 22分16秒 / 62KB / $0。codex exec -m gpt-6-astra "$(cat お題.txt)" で投げています。

【実行条件・厳守】
1. 出力は「単一のHTMLファイル」を1つだけ。同じフォルダに models/ と narration/ がある前提でよい。
   ファイル名は index.html にすること。
2. コードは省略禁止。「// 以下同様」「...(省略)」は禁止。全文を書ききる。
3. three.js は使ってよい(CDN可)。GLTFLoader / DRACOLoader / AnimationMixer /
   InstancedMesh / EffectComposer も可。★ただしバージョンは固定して書くこと。
   DRACOLoader のデコーダも CDN のパスを明示すること(glb は draco 圧縮済み)。
4. 起動時のコンソールエラーは0件であること。**かつ canvas が実サイズで描画されていること**。
5. 画面にHUDを常時出す: FPS / 時刻 / 局面 / 両軍残存 / 表示中の部隊数 / 描画中の兵の数。
6. 最後に「仕様チェックリスト」を表で出し、各項目に ✅/❌ を自己申告する(嘘の✅は最も減点される)。

【使える素材】
models/ashigaru_yari.glb     足軽(槍)  19ボーン・アニメ名 "preset:walk"
models/ashigaru_hata.glb     旗持ち足軽  同上
models/ashigaru_teppo.glb    鉄砲足軽    同上
models/samurai_taisho.glb    武将        同上
models/kiba_musha.glb        騎馬武者    同上
narration/00.mp3 〜 06.mp3   ナレーション(00=導入、01〜06=各局面)

※ glb はすべて Draco 圧縮済み・Y-up・1ユニット≒1メートル相当。
※ 各 glb は1メッシュ1スキンで、アニメーションクリップは "preset:walk" の1本だけ。

【お題】桶狭間の戦いを、兵の一人ひとりが見える3Dシミュレーションにしろ。

1560年、尾張・桶狭間。今川軍の本陣を織田軍が急襲した合戦を再現する。
数字や経緯には諸説があるので、断定せず「通説では」と明記すること。

必須仕様:

■ 地形
- 丘陵・谷・街道・林を手続き的に生成する(起伏のある地形メッシュ。平らな板にしない)
- 高低差が戦況に意味を持つこと(低い谷は上から見えにくい等)
- 等高線の表示をON/OFFできる

■ 兵
- ★兵は一人ひとり3Dモデルで描く。models/*.glb を読み込んで使う
- ★近くの兵は歩行アニメーション("preset:walk")を再生する
- 遠くの兵は簡略表示に切り替えてよい。★描画中の兵の数をHUDに出す
- 織田軍6部隊・今川軍8部隊の計14部隊。部隊ごとに旗の色を変える
- 部隊の規模に応じて兵の数を変える(通説では織田約2,500・今川約20,000)

■ 動き(ここが主題)
- ★部隊は経路に沿って実際に移動する。兵は隊列を組み、地形の起伏に沿って上下する
- ★兵の向きは進行方向を向く。停止時は敵の方向を向く
- 接敵したら隊列が崩れる(密集→散開)
- ★部隊の位置は時刻の関数として決まる(同じ時刻なら必ず同じ配置)

■ 時間
- 再生 / 一時停止 / 早送り(x1 x2 x4 x8) / 巻き戻し。スライダーで任意の時刻へ飛べる
- 合戦を6つの局面に区切る。局面が変わると見出しと解説が出る
  01 集結 12:00 / 02 前進 12:20 / 03 豪雨 12:40 / 04 接敵 13:00 / 05 急襲 13:20 / 06 離脱 13:40
  (12:00→14:00 を 360秒で再生する)

■ 解説(★1つのコンテンツとして完結させる)
- ★局面ごとに解説カードを出す。何が起きているか・なぜそうなったかを2〜3行で
- ★地図に進軍の矢印を描く。どの部隊がどこへ向かっているかが俯瞰で分かること
- ★narration/NN.mp3 を局面に同期して再生する。ON/OFFボタンを付ける
  (00が導入、01〜06が各局面。★ボタンを押すまで鳴らさない=自動再生制限のため)
- 部隊をクリックすると、部隊名・兵数・その時刻の行動が出る

■ カメラ
- 俯瞰(戦場全体) / 追尾(選んだ部隊を追う) / 現地(地上を歩く) の3つを切り替えられる
- ★局面が切り替わったら、その局面の見せ場へカメラが寄る(自動演出。OFFにもできる)

■ 天候
- 豪雨の演出。雨のON/OFFで視界と「見えている部隊の数」が変わる
- 局面が進むと雨脚が強まる

■ 性能
- 兵の総数が多いので、遠景はインスタンス描画にするなど描画負荷を抑える工夫をすること
- HUDのFPSが落ちたら簡略表示へ自動で切り替える仕組みを入れてよい

③ GPT-6 Astra — 京都(町人が歩く街)

実測: 生成 35分32秒 / 74KB / $0。codex exec -m gpt-6-astra "$(cat お題.txt)" で投げています。

【実行条件・厳守】
1. 出力は「単一のHTMLファイル」を1つだけ。ファイル名は index.html。
   同じフォルダに models/ がある前提でよい。
2. コードは省略禁止。「// 以下同様」「...(省略)」は禁止。全文を書ききる。
3. three.js は使ってよい(CDN可)。★バージョンは固定して書くこと。
   GLTFLoader / DRACOLoader / AnimationMixer / InstancedMesh も可。
   ★DRACOLoader のデコーダも CDN のパスを明示すること(glb は draco 圧縮済み)。
4. 起動時のコンソールエラーは0件であること。**かつ canvas が実サイズで描画されていること**。
5. HUDを常時出す: FPS / 建物数 / 三角形数 / 歩いている人の数 / 時刻 / 座標。
6. 最後に「仕様チェックリスト」を表で出し、各項目に ✅/❌ を自己申告する(嘘の✅は最も減点される)。

【使える素材】
models/kyoto_man.glb    町人(男)  19ボーン・アニメ名 "preset:walk"
models/kyoto_woman.glb  町人(女)  同上
models/kyoto_monk.glb   僧侶      同上
models/kyoto_child.glb  子供      同上
※ Y-up・1ユニット≒1メートル相当・1メッシュ1スキン・クリップは "preset:walk" の1本だけ。

【お題】京都の街を、人が生活している街にしろ。

歩ける・飛べる3Dの京都に、人を歩かせる。

必須仕様:

■ 街
- 建物・道路・川・山をコードで手続き的に生成する(建物は最低300棟)
- ランドマークを最低5つ、それと分かる形で作る
  (五重塔・鳥居の並び・木造の橋・城・寺の伽藍)
- ★固定シードを使う。同じシードなら必ず同じ街が建つこと。シード値を画面に出す
- 昼と夜を切り替えられる。夜は窓と提灯に明かりが灯る
- 遠景に山並み、空に時間帯に応じた色のグラデーション

■ 人(★ここが今回の主題)
- ★models/*.glb を読み込み、町人が通りを歩く。歩行アニメーション("preset:walk")を再生する
- ★最低200人。通りに沿って歩き、建物にはぶつからない
- ★人は道の上だけを歩く(建物の中や川の上を歩かない)
- ★すれ違うときに互いを避ける。立ち止まって話している組がいてもよい
- 時間帯で人の数と行き先が変わる(朝は多い・夜は少なく提灯を持つ等)
- 人をクリックすると、その人を追いかけるカメラに切り替わる
- ★歩いている人の数をHUDに出す

■ カメラ
- 歩く / 飛ぶ / 人を追う の3つを切り替えられる(WASD移動・マウスで視点)
- 名所をクリックすると名前と説明のカードが出て、「近くへ飛ぶ」ができる

■ 性能
- 人が多いので、遠くの人は簡略表示にするなどして描画負荷を抑える
- FPSが落ちたら自動で人数を減らす仕組みを入れてよい

④ GPT-6 Astra — 渋谷 CROSSING LAB(何人まで安全か)

実測: 生成 24分03秒 / 66KB / $0。codex exec -m gpt-6-astra "$(cat お題.txt)" で投げています。

【実行条件・厳守】
1. 出力は「単一のHTMLファイル」を1つだけ。ファイル名は index.html。
   同じフォルダに models/ がある前提でよい。
2. コードは省略禁止。「// 以下同様」「...(省略)」は禁止。全文を書ききる。
3. three.js は使ってよい(CDN可)。★バージョンは固定して書くこと。
   GLTFLoader / DRACOLoader / AnimationMixer / InstancedMesh も可。
   ★DRACOLoader のデコーダも CDN のパスを明示すること(glb は draco 圧縮済み)。
4. 起動時のコンソールエラーは0件であること。★かつ canvas が実サイズで描画されていること。
5. ★FPSは「実経過時間」から計算すること。
   シミュレーションの時間刻みに上限を掛けるのは構わないが、
   **その上限を掛けた値でFPSを計算してはいけない**(重い環境でFPSが実際より高く出る)。
6. 最後に「仕様チェックリスト」を表で出し、各項目に ✅/❌ を自己申告する(嘘の✅は最も減点される)。

【使える素材】
models/shibuya_suit.glb     会社員(スーツ)  19ボーン・アニメ名 "preset:walk"
models/shibuya_woman.glb    若い女性        同上
models/shibuya_student.glb  学生            同上
models/shibuya_shopper.glb  買い物客        同上
※ Y-up・1ユニット≒1メートル相当・1メッシュ1スキン・クリップは "preset:walk" の1本だけ。

────────────────────────────────────────────────
【お題】「何人までなら安全に渡れるのか」を確かめる交差点シミュレーターを作れ。
────────────────────────────────────────────────

■ このシミュレーターが答える問い(★これが主題。見た目より優先する)

  「人数と信号周期をこう設定したとき、この交差点は安全か、危険か」

  使う人は、イベント主催者・施設管理者・自治体の担当者を想定する。
  「規制をかけるべきか」「何人で止めるべきか」の判断材料を出すのが目的。
  ★見て楽しいものではなく、**答えが出る**ものにすること。

■ 判定の基準(★国際的に使われている指標を使う)

  歩行者の密度(人/m²)で危険度を判定する。Fruin の Level of Service を使う:
    LOS A  〜0.31 人/m²   自由に歩ける
    LOS B  〜0.43         ほぼ自由
    LOS C  〜0.72         他人を意識して進路を選ぶ
    LOS D  〜1.08         流れに従うしかない
    LOS E  〜2.17         歩行速度が大きく落ちる・接触が起きる
    LOS F  2.17〜         流れが止まる・★群集事故の危険域
  ※ この数値は一般に公開されている基準。画面に「Fruin LOS」と出典を明記すること。

■ ★主役の表示=密度のヒートマップ(ここが一番大事)

  - ★交差点の路面を格子(1メートル四方程度)に分け、**各マスの人/m²を色で塗る**
  - 色は LOS に対応させる(A〜C=緑〜黄、D=橙、E=赤、F=濃赤)。凡例を必ず出す
  - ★**このヒートマップが画面の主役**。俯瞰で見たとき、どこが危ないか一目で分かること
  - 人の3Dモデルはヒートマップの上に乗る「証拠」として描く(主役ではない)
  - 危険域(LOS E以上)のマスの面積と、そこにいる人数を常時表示する

■ 出すべき数字(HUDに常時)

  - 現在の最大密度(人/m²)と、その LOS 等級
  - ★**危険域(E以上)の面積 m² と、そこにいる人数**
  - 交差点にいる人数 / 渡り終えた人数 / 歩道で待っている人数
  - 1周期あたりの平均待ち時間(秒)
  - FPS(★実経過時間から計算したもの)

■ 操作(★動かすと答えが変わること)

  - ★人の流入量スライダー: 10〜3,000人/分。増やすと危険域が広がる
  - ★信号周期スライダー: 30〜120秒。短くすると滞留が増え、長くすると一度に渡る人が増える
  - ★「規制をかける」ボタン: 流入を制限したとき、危険域がどう消えるかを見せる
  - 時間帯プリセット: 平常 / 週末 / イベント時(カウントダウン等の想定)
  - 再生 / 一時停止 / ×1 ×2 ×4

■ ★結果の要約(見た人が持ち帰れるように)

  - 画面のどこかに、いまの設定に対する**一文の判定**を出す
    例:「この設定では最大 2.4 人/m²(LOS F)。★危険域が 38 m² 発生しています」
       「この設定では最大 0.9 人/m²(LOS D)。流れは保たれています」
  - ★**何人を超えると LOS E に入るか**を自動で探して表示する(しきい値の提示)

■ 場所

  - 五叉路のスクランブル交差点を手続き的に生成する。横断歩道・歩道・車道・建物
  - 周囲のビルの広告は★実在の企業名やロゴを出さない。抽象的な色と形にする
  - 昼 / 夕方 / 夜。ただし★見た目より**ヒートマップの視認性**を優先すること

■ 人

  - models/*.glb を読み込み、歩行アニメーション("preset:walk")を再生する
  - ★近景では実モデル、遠景は簡略表示でよいが、
    **近景に実モデルが十分な数(数十体)出るようにする**。
    ⚠ 歩行者目線にしたとき、目の前が簡略表示だらけにならないこと
  - 人は互いを避ける。密度が上がると速度が落ちる(LOS の定義に沿わせる)

■ カメラ

  - ★既定は「俯瞰」。ヒートマップ全体が見えること
  - 俯瞰 / 歩行者目線 / 定点 を切り替えられる

■ 性能

  - 3,000人まで出せるように、遠景は簡略表示にするなど描画負荷を抑える
  - FPSが落ちたら自動で簡略化してよい(★ただし判定に使うFPSは実経過時間から計算すること)

■ ⚠ 断り書き(必ず画面に出す)

  - この交差点の形状・寸法は模式的なものであり、実在の交差点の測量値ではないこと
  - 密度の判定基準は Fruin の Level of Service を用いていること
  - このシミュレーションは検討の材料であり、実際の安全判断は現地の状況によること

■ ★渋谷だけ、お題を2回書きました

1本目は「渋谷スクランブル交差点の人の流れを再現しろ」と書きました。動くものは出てきましたが、何の問いにも答えていませんでした。

2本目は「何人までなら安全に渡れるのかを確かめる交差点シミュレーターを作れ」に変え、使う人・判定基準(Fruin の Level of Service)・主役の表示(密度のヒートマップ)まで指定しました。上に載せているのは2本目です。

「再現しろ」と書くと、再現しか返ってこない。
「この問いに答えろ」と書くと、答えを出す道具が返ってくる。

12. 「京都らしい街」から「京都そのもの」へ

ここまでは、AIが想像で建てた京都でした。次に投げたお題は「実在の地図をもとに忠実に再現しろ」です。生成をやめて、実在のデータに差し替えました。

実在の地図から建てた京都・八坂通。奥に八坂の塔、手前を町人が歩いている
八坂通から八坂の塔へ。建物の輪郭も道の曲がりも、実在データそのまま
データ出どころ量
建物の輪郭OpenStreetMap6,864棟
街路OpenStreetMap1,511本(石段102本を含む)
水系・ランドマークOpenStreetMap40/44
地形(標高)国土地理院 標高タイル129×129格子・35m→98m
範囲祇園〜清水1,369 × 1,220 m

■ ⚠ 実在データを使うなら、出典表示は義務です

OpenStreetMap は ODbL(オープン・データベース・ライセンス)、国土地理院のタイルは出典明記が条件です。画面上に常時表示しています。使うだけなら無料ですが、表示を消すとライセンス違反になります。

「京都らしい街」と「京都そのもの」は、必要なデータがまったく違う。
前者はプロンプト、後者は出典のあるデータ。

13. 桶狭間は、平地ではありませんでした

最初に作った桶狭間は、ほぼ平らな地面の上でした。実際は違います。国土地理院の標高から作り直しました。

実地形の桶狭間。等高線と断面図、兵896体と得物が表示されている
等高線・断面図つき。地形の誇張は ×1/×2/×3 で切り替えられる
項目実測
地形データ国土地理院 標高タイル 161×161格子/2,189 × 2,219 m
標高の幅13.1 m 〜 55.9 m(高低差 42.8 m)
行軍路の断面水平 1,572 m/高低差 20.0 m/上り 14.4 m・下り 31.8 m
兵896体(10種)/得物 564本(槍332・長柄116・弓87・鉄砲29)
説の切り替え①迂回奇襲説 ②正面攻撃説 ― 同じ地形の上で両方を動かせる

「どちらが正しいか」をAIに決めさせない。
同じ地形の上で両方動かして、見た人が判断できる形にする。

14. AIが作った人は、そのままでは歩けません

Tripo は文章から3Dモデルを作り、骨(リグ)まで入れてくれます。ところが着物の人物だけ、腕の骨が1本もできませんでした。袖が体と一体化していて、自動リグが腕を分離できないからです。

■ 得物を持たせると、リグが壊れます

槍・弓・鉄砲を持った兵は、3種とも2回ずつ、計8回すべて失敗しました。得物を外した素体は4体とも一発で成功。細長い持ち物を、自動リグは手足と誤認します。

★ もうひとつ。手首のマーカーを袖の先に置いたら、そこを手首だと信じて前腕が長く作られ、腕が膝まで伸びました(腕÷身長 0.45〜0.52)。シルエットの端から身長の9%内側に置き直して 0.31〜0.41 に収まりました。

15. ★「直した」と3回言って、3回とも直っていませんでした

この回でいちばん恥ずかしく、いちばん役に立つ話です。症状はずっと同じでした ― 街の人が、こちらを向いたまま前へ滑っていく。

修正後の歩行者を正面から見たところ。顔が進行方向を向いている
修正後。カメラを進行方向の前に置くと顔が見える=正しく前を向いて歩いている

真因は、たった1行でした。

const PEOPLE_YAW = PEOPLE_TYPES.map(() => Math.PI);   // 全モデルを180°回して焼く

この π は前の素材(Tripo)の向きに合わせて置いた数字です。素材を Mixamo に差し替えたとき、Mixamo 側はもともと正面を向いていたので、π を掛けた瞬間に背中が進行方向を向きました。背中が前、足は逆向きに掃く、体は前へ進む ― だから「前を向いたムーンウォーク」になります。

なぜ3回も見抜けなかったか ― 測り方が3回とも間違っていた

やった測り方なぜ嘘をつくか
位相と移動距離の整合を見る自分の計算の中だけで閉じている。向きが180°ずれていても整合する
元のモデルで正面を測る画面に出ているのは焼き込んだ後。180°回した後を測っていない
足の最低点で接地足を判定歩行中の遊脚のつま先は地面すれすれを通る。左右差は数ミリで判別できない

どれも「それっぽい数字が出る測り方」でした。
数字が出ると、人はそこで安心して止まってしまう。

正しい測り方は1つだけ ― 地面に着いた足は、世界で止まっているはず

歩幅(身長1あたり)0.700.870.971.151.30
滑り率31%9%2%12%22%

向きを直す前は、どの歩幅を入れても最良21〜29%でした ― つまり歩幅の問題ではなかった、という証拠です。向きを直したあとの最小値 0.97 は、クリップから実測した歩幅(0.964/0.982/0.969)とぴたり一致しました。

■ 恒久対策 ― 定数をやめ、モデルから測る

素材を差し替えても、壊れたら画面が自分で申告します。

品質を、人の注意力ではなく仕組みに守らせる。
第1回から通している話の、いちばん痛い実演になりました。

16. ★結論 ― 縛りは制限ではなく、測定装置だった

縛ったのは、意地悪をするためではありませんでした。

投げた質問見えるもの
前半「何もなしで、何ができる?」実力
後半「全部使っていい。どこまで行ける?」到達点

指示の設計とは、「何を禁じて、何を許すか」を決めることでした。

お題を凝るより、その1行を決めるほうが結果が変わる ― 前半の結論と、ここでつながります。


■ 何も映らないときは、ほぼ WebGL2 です

無効だと画面に「WebGL2に対応したブラウザで開いてください」と出て、HUDの数字が全部 — になります。

  1. chrome://gpu を開く → WebGL2: Hardware accelerated になっているか見る
  2. なっていなければ 設定 → システム →「ハードウェア アクセラレーションが使用可能な場合は使用する」をON → 再起動