これは開発初日(2026-06-07)の全作業記録です。決定・変更・コマンド・ファイルパス・つまずきを、後から追試できる粒度で残します。
今日やったこと:全体像
大きく三段階で進めました。
- M0:基盤構築(Astro 土台・ライセンス・リポジトリ初期化)
- MVP-0+:スキーマ+CCRsデモ(JSONスキーマ v0.1.0 確立・代表シナリオ JSON 作成・2D俯瞰デモ稼働)
- MVP-0:ブログ/ニュース基盤(コンテンツコレクション・一覧/個別ページ・レイアウト・初期記事)
三段階は必ずしも逐次ではなく、後述する意思決定と並行して進みました。以下、順番に詳細を記します。
M0:基盤構築
技術選定とその理由
フレームワークは Astro 6 を採用しました(astro: ^6.4.4)。採用理由は次のとおりです。
- 静的出力(SSG)がデフォルト:サーバーを持たず Cloudflare Pages へのデプロイが素直に実現できる。
astro.config.mjsでoutput: 'static'を設定するだけです。 - アイランドアーキテクチャ:Three.js などの重いシミュレーターを必要な箇所だけクライアントに渡せる。それ以外は完全静的 HTML になりキャッシュ・SEO が有利です。
- Content Layer API:
src/の外に置いたcontent/配下の Markdown をコンテンツコレクションとして型付きで扱える。
配信先を Cloudflare Pages にした理由は、広告・寄付・アフィリエイトを将来的に有効にするためです。GitHub Pages・Vercel は無料枠の商用利用制限があるため選外にしました(governance/decision_log.md #001)。
ライセンス構成
LICENSE # ソースコード:MIT
DATA_LICENSE.md # データ(data/ 配下):CC BY 4.0
ATTRIBUTIONS.md # 利用 OSS・データ出典一覧
コードを MIT、データを CC BY 4.0 とした理由は「再利用・MATLAB 読み込みまで含めた完全公開」がプロジェクトの価値の核だからです。著作権表示は個人名露出を避け crash-prevention-site contributors としています。
ローカルで動かすコマンド
# 初回(依存パッケージのインストール)
npm install
# 開発サーバー起動(http://localhost:4321)
npm run dev
# 本番ビルド(dist/ に静的出力)
npm run build
# ビルド結果のプレビュー
npm run preview
package.json のスクリプト定義は astro dev / astro build / astro preview に対応しており、type: "module" の ESM 環境です。
MVP-0+:スキーマ+CCRsデモ
JSONスキーマ v0.1.0 の確立
data/schema/scenario.schema.json を v0.1.0 として確立しました。このファイルがコンテンツ・シミュレーター・シナリオDBの 三トラック全てが参照する単一の真実 です(decision_log.md #004)。
主要構造(抜粋):
| フィールド | 概要 |
|---|---|
id / title / short_code | シナリオ識別・表示用情報 |
category | 事故類型(car-to-car-rear 等の enum) |
source | 一次出典(organization / document / url / confirmed_date) |
coordinate_system | 座標系定義(length_unit, x_axis, y_axis, origin) |
actors[] | 登場物(role=subject/target、motion の oneOf) |
geometry | 配置・重なり率 |
parameters[] | スライダで可変にする入力パラメータ定義 |
avoidance_requirements | 回避必須要件(Phase1 は運動学のみ) |
motion の型は stationary / constant_velocity / constant_deceleration / profile の4種を JSON Schema の oneOf で定義しています。profile 型はタイムスタンプ付き位置サンプル列で、将来の詳細軌跡に対応します。
スキーマの変更は統括が調停し、配下セッション(S-sim/S-scenario)は単独変更不可というルールにしています。これはシミュレーターとシナリオDBが同じデータ契約を共有するためです。
代表シナリオ:CCRs(Car-to-Car Rear stationary)
data/scenarios/euro-ncap/ccrs.json を作成しました。Euro NCAP AEB C2C プロトコルの CCRs(停止車両への後方追突)シナリオです。
データの取り扱い方針:数値は事実データとして再構成し出典を明記しています。プロトコル文書の文章・図表・写真の転載はしていません。出典は source.url に Euro NCAP プロトコル一覧ページを掲載し、confirmed_date: "2026-06-07" を記録しています(PDF 直リンクは版によって URL が変動するため安定エントリを使用)。
シナリオの骨格:
- 自車(VUT)が等速で前方の停止車両(GVT)に接近する
- 試験速度帯は City 10–50 km/h・Inter-Urban 30–80 km/h(5/10 km/h 刻み)
- 重なり率 100%(100% overlap)・横オフセット 0
スライダで可変にしたパラメータは次の3つです:
| キー | ラベル | 範囲 | デフォルト |
|---|---|---|---|
vut_speed_kmph | 自車の接近速度 | 10–80 km/h, 5刻み | 40 km/h |
initial_gap_m | 初期車間距離 | 10–80 m, 1刻み | 40 m |
brake_capability_g | 想定ブレーキ能力 | 0.3–1.0 g, 0.05刻み | 0.9 g |
「回避必須要件」の考え方:最終制動点
CCRs デモで可視化している核心は 最終制動点(latest braking point) です。
停止対象への追突を回避するのに必要な制動距離は、一定減速度 a〔m/s²〕での等減速を仮定すると次式で求まります。
必要制動距離 = v² / (2a)
- v:現在の速度〔m/s〕
- a:想定ブレーキ能力〔m/s²〕(=
brake_capability_g× 9.81)
現在の車間がこの値を下回った瞬間に「その減速度では物理的に停止できない」状態になります。この境界が最終制動点です。
実装は src/sim/core/kinematics.ts の lastChanceGapM 関数:
export const lastChanceGapM = (speedKmph: number, brakeG: number): number => {
const v = kmphToMps(speedKmph); // km/h → m/s
const a = brakeDecelMps2(brakeG); // g → m/s²(= brakeG × 9.81)
return a > 0 ? (v * v) / (2 * a) : Infinity;
};
デモでは「現在の車間 ≥ 最終制動点までの距離」を満たす間は「回避可能(要制動)」、下回ると「回避不能(最終制動点を通過)」と表示します。速度を上げる・ブレーキ能力を下げると最終制動点が自車に近づき、回避できる時間的余裕が急速に縮まる様子が一目でわかります。
なお現段階は Phase 1(運動学のみ) です。センサの検知遅れ・AEB の反応遅延・ジャーク・路面摩擦変動などは Phase 2/3 以降で追加します。
kinematics.ts の役割
src/sim/core/kinematics.ts は後方追突シナリオの運動学を扱う 純粋関数群(副作用なし)です。主な公開関数:
| 関数 | 内容 |
|---|---|
kmphToMps | km/h → m/s 変換 |
brakeDecelMps2 | ブレーキ能力 [g] → 減速度 [m/s²] |
lastChanceGapM | 現速度・ブレーキ能力での必要制動距離 |
collisionTimeS | 一定速度での衝突予測時間 |
rearEndStateAt | 時刻 t における状態(位置・車間・TTC・必要減速度・回避可否) |
timeToLastChanceS | 最終制動点に達するまでの時間 |
このモジュールは Astro コンポーネント(OverheadDemo.astro)の <script> タグからインポートされ、クライアント JS としてバンドルされます。
OverheadDemo.astro の役割
src/components/OverheadDemo.astro は2D俯瞰デモの全体を担う Astro コンポーネントです。役割を整理すると:
- ビルド時:
ccrs.jsonを静的 import してスライダの初期値・ラベル・range 属性を HTML に展開する - 実行時(クライアント):
kinematics.tsを import し、スライダ操作・再生・時刻シークに応じて Canvas をrequestAnimationFrameで再描画する - Canvas 描画内容:路面・自車(青矩形)・停止対象(灰矩形)・最終制動点(橙破線)・車間の寸法線・HUD(速度/車間/TTC/必要減速度/回避判定)
シナリオデータは <script type="application/json" id="scenario-data"> で HTML に埋め込まれ、クライアント JS から JSON.parse で取り出す設計です(Astro のサーバー/クライアント境界を跨いでデータを渡す典型パターン)。
content.config.ts の役割
src/content.config.ts は Astro の Content Layer API を使ってコンテンツコレクションを定義します。blog と news の2コレクションを定義しており、それぞれ content/blog/ と content/news/ の Markdown を glob で拾います。
frontmatter の型は Zod スキーマで定義しています(主なフィールド):
// blog コレクション(このファイルも準拠)
z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
author: z.string().default('編集部'),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
sources: z.array(sourceSchema).default([]),
})
この型定義があるため、frontmatter のフィールド名の誤りはビルド時に即座にエラーになります。
つまずき・学んだこと
ダークモードで Canvas 背景が暗転する
OS のダークモードが有効な環境で Canvas 領域が意図せず暗い背景色になり、路面・車両の色が見づらくなる問題が起きました。Canvas 自体は HTML 要素なので CSS の color-scheme や OS ダークモード設定の影響を受けます。明示的に background: #ffffff を CSS で指定することで解決しました。
.demo__canvas {
background: #ffffff;
}
requestAnimationFrame の無駄なループを回避
当初の実装では常に requestAnimationFrame を回し続ける設計になっていました。一時停止中・シーク中でも描画ループが走り続けるのはコストの無駄なので、「再生中のみ requestAnimationFrame を次フレームに渡す」設計に変更しました。
function frame(ts: number) {
if (!playing) return; // 再生中でなければ即リターン
// ... 状態更新・描画 ...
requestAnimationFrame(frame); // 再生中だけ次フレームを予約
}
スライダ操作やシークでの描画は draw() を直接呼ぶことで、ループを起こさず単発で更新します。
リポジトリ構成の要点
crash-prevention-site/
├── src/
│ ├── components/
│ │ └── OverheadDemo.astro # 2D俯瞰デモ(Canvas + controls)
│ ├── sim/
│ │ └── core/
│ │ └── kinematics.ts # 運動学コア(純粋関数群)
│ └── content.config.ts # コレクション型定義(Zod)
├── content/
│ ├── blog/ # 開発ブログ(S-content の書込境界)
│ └── news/ # 週次ニュース(同)
├── data/
│ ├── schema/
│ │ └── scenario.schema.json # スキーマ v0.1.0(単一の真実)
│ └── scenarios/
│ └── euro-ncap/
│ └── ccrs.json # CCRs 代表シナリオ
├── astro.config.mjs # output: 'static'
└── package.json # astro ^6.4.4, scripts: dev/build/preview
次にやること(3トラック並行)
初日の作業を終えて、以下の3トラックを並行して進めます。
| トラック | 担当 | 直近のゴール |
|---|---|---|
| 開発日誌継続 | S-content | 節目ごとに追試可能な粒度で記録 |
| シナリオDB拡充 | S-scenario(MVP-1 着手時に起票) | CCRs に続く Euro NCAP / JNCAP シナリオの構造化 |
| シミュレーター拡張 | S-sim | Phase2(センサ検知可視化)・Phase3(AEB 制動)への段階的展開 |
公開(Cloudflare Pages へのデプロイ・GitHub 公開リポジトリ化)は別途ユーザー承認後に実施します。現時点はローカル稼働の段階です。
出典:data/scenarios/euro-ncap/ccrs.json(Euro NCAP AEB C2C Test Protocol, 確認日 2026-06-07)。CCRs の試験速度帯・重なり率等の数値は事実データとして再構成したものです。原文の文章・図表は転載していません。