開発ブログ

開発日誌(1) 基盤構築からCCRsデモ・ブログ基盤まで

これは開発初日(2026-06-07)の全作業記録です。決定・変更・コマンド・ファイルパス・つまずきを、後から追試できる粒度で残します。

今日やったこと:全体像

大きく三段階で進めました。

  1. M0:基盤構築(Astro 土台・ライセンス・リポジトリ初期化)
  2. MVP-0+:スキーマ+CCRsデモ(JSONスキーマ v0.1.0 確立・代表シナリオ JSON 作成・2D俯瞰デモ稼働)
  3. MVP-0:ブログ/ニュース基盤(コンテンツコレクション・一覧/個別ページ・レイアウト・初期記事)

三段階は必ずしも逐次ではなく、後述する意思決定と並行して進みました。以下、順番に詳細を記します。


M0:基盤構築

技術選定とその理由

フレームワークは Astro 6 を採用しました(astro: ^6.4.4)。採用理由は次のとおりです。

  • 静的出力(SSG)がデフォルト:サーバーを持たず Cloudflare Pages へのデプロイが素直に実現できる。astro.config.mjsoutput: 'static' を設定するだけです。
  • アイランドアーキテクチャ:Three.js などの重いシミュレーターを必要な箇所だけクライアントに渡せる。それ以外は完全静的 HTML になりキャッシュ・SEO が有利です。
  • Content Layer APIsrc/ の外に置いた 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.jsonv0.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.tslastChanceGapM 関数:

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 は後方追突シナリオの運動学を扱う 純粋関数群(副作用なし)です。主な公開関数:

関数内容
kmphToMpskm/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 を使ってコンテンツコレクションを定義します。blognews の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-simPhase2(センサ検知可視化)・Phase3(AEB 制動)への段階的展開

公開(Cloudflare Pages へのデプロイ・GitHub 公開リポジトリ化)は別途ユーザー承認後に実施します。現時点はローカル稼働の段階です。


出典:data/scenarios/euro-ncap/ccrs.json(Euro NCAP AEB C2C Test Protocol, 確認日 2026-06-07)。CCRs の試験速度帯・重なり率等の数値は事実データとして再構成したものです。原文の文章・図表は転載していません。

← 開発ブログの一覧へ