# Navigator サービス設計仕様書
## 概要
Navigator(navigator.glre.dev)はボクセルベースの 3D マップサービスであり、
都市をナビゲーション可能なボクセル空間としてブラウザ上にレンダリングする。
Cloudflare Workers 上の Hono バックエンドと React フロントエンドを組み合わせ、
GLRE による GPU アクセラレーテッドインスタンスボクセルレンダリングと、
Web Worker を介した HPA\*経路探索による 3D ルート検索を実現している。
```
Architecture Overview:
┌───────────────────────────────────────────────────────┐
│ Browser │
│ ┌─────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ │
│ │ React │ │ Canvas │ │ Voxel │ │ HPA* │ │
│ │ UI │ │ (GLRE) │ │ Worker │ │ Worker │ │
│ └────┬────┘ └────┬─────┘ └─────┬─────┘ └─────┬────┘ │
│ │ │ │ │ │
│ └─────────┬─┴─────────────┴─────────────┘ │
│ │ SWR + Atoms │
└─────────────────┼─────────────────────────────────────┘
│ HTTPS
┌─────────────────┼──────────────────────────────────────┐
│ Cloudflare Workers │
│ ┌──────────────┴──────────────┐ │
│ │ Hono (index.tsx) │ │
│ │ ├── Auth.js (Google OAuth) │ │
│ │ ├── /api/v1/* endpoints │ │
│ │ └── /parties/* (PartyKit) │ │
│ └──────┬──────────┬───────────┘ │
│ │ │ │
│ ┌────┴───┐ ┌────┴────┐ │
│ │ D1 │ │ R2 │ │
│ │ SQLite │ │ Bucket │ │
│ └────────┘ └─────────┘ │
└────────────────────────────────────────────────────────┘
```
## インフラストラクチャスタック
### Cloudflare Workers ランタイム
バックエンドは Cloudflare Workers 上で Hono を HTTP フレームワークとして動作する。
Vite と`@cloudflare/vite-plugin`が開発・本番ビルドの両方を担当する。
Wrangler が`vite build`後に`wrangler deploy`で Cloudflare へデプロイする。
| バインディング | 型 | 用途 |
| ------------------ | -------------- | -------------------------------- |
| `my_d1_navigator` | D1 Database | ユーザーデータ、履歴、リスト |
| `my_r2_navigator` | R2 Bucket | ボクセルアトラスストレージ |
| `v1` (PartyServer) | Durable Object | リアルタイムマルチプレイヤー状態 |
### 認証システム
Google OAuth は Auth.js(`@hono/auth-js`)経由で実装されている。
DrizzleAdapter が Auth.js を D1 SQLite に接続し、セッションを永続化する。
データベースセッションではなく JWT 戦略がセッション管理に使用される。
```
認証フロー:
ブラウザ ──→ /api/auth/signin ──→ Google OAuth ──→ コールバック ──→ JWT
│
/api/v1/* ──→ verifyAuth() ミドルウェア ──→ token.sub 抽出 ──→ userIdとして使用
```
Hono アプリ上の認証ミドルウェアチェーン:
1. `initAuthConfig` - Google プロバイダーと DrizzleAdapter で Auth.js を設定
2. `authHandler()` - `/api/auth/*`ルート(signin、callback、signout)を処理
3. `verifyAuth()` - `/api/v1/*`と`/parties/*`ルートを保護
4. カスタムミドルウェアが PartyServer 接続用に`x-auth-sub`ヘッダーを注入
### リアルタイムレイヤー
PartyServer(`partyserver` + `hono-party`経由)が WebSocket ベースのリアルタイム通信を提供する。
サーバークラスが接続ユーザーを追跡し、位置状態をブロードキャストする。
接続はミドルウェアが注入した`x-auth-sub`ヘッダーを通じて認証される。
```
PartyServer ライフサイクル:
onConnect(conn, ctx) ──→ subでユーザー検索 ──→ setState({ username })
onMessage(conn, msg) ──→ usersマップ更新 ──→ broadcast(JSON.stringify(users))
onClose(conn) ──→ マップから削除 ──→ broadcast(JSON.stringify(users))
```
## データベーススキーマ
### 既存の認証テーブル
`src/schema.ts`の DrizzleAdapter が管理する。
これらのテーブルは Auth.js スキーマ規約に従う。
| テーブル | 主要カラム |
| ------------------- | ----------------------------------------- |
| `user` | id, name, email, emailVerified, image |
| `account` | userId, provider, providerAccountId |
| `session` | sessionToken, userId, expires |
| `verificationToken` | identifier, token, expires |
| `authenticator` | credentialID, userId, credentialPublicKey |
### アプリケーションテーブル
Navigator 固有の機能のためにスキーマを拡張するテーブル群。
| テーブル | カラム | 用途 |
| ---------------- | --------------------------------------------------------------- | ------------------------------ |
| `search_history` | id, userId, placeId, name, address, lat, lng, searchedAt | ユーザーごとの検索履歴 |
| `saved_list` | id, userId, name, icon, createdAt | ユーザー作成の場所リスト |
| `saved_place` | id, listId, placeId, name, address, lat, lng, photoUrl, savedAt | 保存リスト内の場所 |
| `labeled_place` | userId(PK), label(PK), placeId, name, address, lat, lng | ラベルベースの場所ブックマーク |
```
エンティティ関係:
users ──1:N──→ search_history
users ──1:N──→ saved_lists ──1:N──→ saved_places
users ──1:N──→ labeled_places
```
## API エンドポイント
### 認証ルート
Auth.js ミドルウェアが自動的に処理する。
| メソッド | パス | 説明 |
| -------- | ---------------------- | ---------------------- |
| GET | `/api/auth/signin` | OAuth サインインページ |
| GET | `/api/auth/callback/*` | OAuth コールバック処理 |
| POST | `/api/auth/signout` | サインアウト |
| GET | `/api/auth/session` | 現在のセッション情報 |
### ユーザールート
| メソッド | パス | 説明 | レスポンス |
| -------- | ------------ | -------------------------- | ---------------------------- |
| GET | `/api/v1/me` | 現在のユーザープロフィール | `{ username, email, image }` |
### Google Maps プロキシルート
サーバーサイド専用。`GOOGLE_MAPS_API_KEY`環境変数を使用して
Google Maps API を呼び出し、結果をクライアントにプロキシする。
クライアントサイドコードでの API キー露出を防止する。
| メソッド | パス | クエリパラメータ | 説明 |
| -------- | ----------------------------- | ---------------- | --------------------------- |
| GET | `/api/v1/places/autocomplete` | `q`(文字列) | Place Autocomplete プロキシ |
| GET | `/api/v1/places/geocode` | `placeId` | ジオコーディングプロキシ |
### 検索履歴ルート
| メソッド | パス | ボディ / クエリ | 説明 |
| -------- | --------------------- | -------------------------------------- | ------------------------------ |
| GET | `/api/v1/history` | - | 検索履歴一覧取得(最新 30 件) |
| POST | `/api/v1/history` | `{ placeId, name, address, lat, lng }` | 履歴エントリ追加 |
| DELETE | `/api/v1/history/:id` | - | 履歴エントリ削除 |
### 保存リストルート
| メソッド | パス | ボディ / クエリ | 説明 |
| -------- | ----------------------------------- | ------------------ | -------------------- |
| GET | `/api/v1/lists` | - | 保存リスト一覧取得 |
| POST | `/api/v1/lists` | `{ name }` | 新規リスト作成 |
| PATCH | `/api/v1/lists/:id` | `{ name }` | リスト名変更 |
| DELETE | `/api/v1/lists/:id` | - | リスト削除 |
| GET | `/api/v1/lists/:id/places` | - | リスト内の場所取得 |
| POST | `/api/v1/lists/:id/places` | `{ placeId, ... }` | リストに場所を追加 |
| DELETE | `/api/v1/lists/:id/places/:placeId` | - | リストから場所を削除 |
### ラベルルート
| メソッド | パス | ボディ / クエリ | 説明 |
| -------- | ----------------------- | ------------------ | ----------------------- |
| GET | `/api/v1/labels` | - | ラベル付き場所一覧取得 |
| PUT | `/api/v1/labels/:label` | `{ placeId, ... }` | ラベルエントリ追加/更新 |
## ボクセルレンダリングシステム
### Web Mercator タイルマッピング
空間はズームレベル 17 の Web Mercator タイルに分割される。
各タイルは 256x256x256 ボクセルの Region に対応する。
SCOPE 定数が有効なタイル範囲を定義する。
```
座標変換パイプライン:
lat/lng ──→ Web Mercator (i, j) z=17 ──→ ボクセルワールド座標
│
x = (i - SCOPE.x0 + 0.5) * 256
y = カメラ高さ
z = (j - SCOPE.y0 + 0.5) * 256
```
`canvas.tsx`の`mercator2position`関数がタイル座標をワールド空間のカメラ位置に変換する。
OFFSET 定数`[116415, 51623]`がデフォルトのカメラ中心を設定する
(Web Mercator タイルにおける東京エリア付近)。
`REGION = 256`がタイルあたりのボクセル次元数である。
`hooks/useCoordConvert.ts`のユーティリティ関数が双方向変換を処理する:
```
latLngToTile(lat, lng) ──→ [i, j] z=17のWeb Mercatorタイル
latLngToWorld(lat, lng) ──→ [x, y, z] ボクセルワールド座標
worldToLatLng(x, y, z) ──→ [lat, lng] 逆変換
tileToLatLng(i, j) ──→ [lat, lng] タイル中心から地理座標
```
voxelized-js の`posOf(worldX, worldZ)`と`localOf(x, y, z, i, j)`が
ワールド座標と Region ローカル座標の間の変換を行う。
### ボクセルデータパイプライン
ボクセルデータは R2 に保存されたアトラステクスチャとしてエンコードされる。
Voxel Worker(`voxel.ts`)が`voxelized-rs`(Rust WASM)経由のグリーディメッシングを実行し、
占有グリッドを描画用メッシュデータに変換する。
```
アトラスローディング:
R2 ──→ アトラス画像取得 ──→ 占有グリッドにデコード ──→ グリーディメッシュ (WASM)
│
インスタンス描画コール
```
### インスタンスレンダリングパイプライン
GLRE ノードシステムがインスタンスボックスレンダリング用のシェーダーを生成する。
各ボクセルインスタンスは位置(`pos`)、スケール(`scl`)、アトラス ID(`aid`)を持つ。
Morton 曲線エンコーディングが 3D ボクセル座標を 2D アトラス UV に変換し、
`texelFetch`による色参照を行う。
```
シェーダーパイプライン (createNode):
インスタンスデータ (pos, scl, aid)
│
├── 頂点: MVP * (boxVertex * scl + pos + offset[aid])
│
└── フラグメント: texelFetch(atlas[aid], morton2uv(vCenter)) * diffuse(normal)
```
最大 16 のアトラステクスチャ(`iAtlas0`..`iAtlas15`)と対応するオフセット
(`iOffset0`..`iOffset15`)が同時にサポートされる。
### パスレンダリング
パスレンダラー(`createPath`)が HPA*の結果を色付きボックスインスタンスとして描画する。
色はインスタンスインデックスに基づいて青(出発地)から赤(目的地)に補間される。
パス位置は HPA* Worker の結果からインスタンスアトリビュートとしてアップロードされる。
## 経路探索システム
### HPA\* アルゴリズム
HPA*(Hierarchical Pathfinding A*)システムは Web Worker(`worker.ts`)内で完全に実行される。
16x16x16 サイズのクラスターに分割されたボクセル占有データ上で動作する。
```
HPA* Pipeline:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Load Occ │ ──→│ Build Graph │ ──→│ Abstract A* │
│ (atlas) │ │ (clusters) │ │ (high-level)│
└──────────────┘ └──────────────┘ └──────┬───────┘
│
┌──────┴───────┐
│ Refine Path │
│ (low-level) │
└──────────────┘
```
1. **Region 読み込み**: タイルごとにアトラス画像から占有グリッドを読み込み
2. **グラフ構築**: クラスター境界ノードをシードし、クラスター内 BFS でエッジ重みを計算
3. **抽象探索**: 高レベルクラスターグラフ上の A\*で近似パスを発見
4. **パス精緻化**: バウンディングボックス内の低レベル BFS でボクセル精度のパスを生成
### メモシステム
Worker はグラフデータの再構築を回避するために Region ごとのメモキャッシュを保持する。
メモにはノード、エッジ、クラスター境界、構築フラグが含まれる。
メモはメインスレッドにエクスポートされ、シーンマップ上にキャッシュされ、
経路探索リクエスト間で再利用される。
### クロス Region 経路探索
出発地と目的地が異なるタイルにある場合、Worker は以下を実行する:
1. 出発地と目的地のオフセット間の Bresenham 線上の全タイルを計算
2. 隣接接続性のために各方向に 1 タイル拡張
3. 必要な全 Region を読み込み(メモ再利用または新規アトラス取得)
4. 隣接 Region 間の境界ノードを接合
5. 全マルチ Region グラフに対して HPA\*を実行
## フロントエンドアーキテクチャ
### コンポーネントツリー
```
App
├── Loading (初期ローディングインジケーター、メッシュ準備完了時に削除)
└── PC
├── Canvas (GLRE WebGLキャンバス、ボクセル+パスレンダリング)
├── Overlay[nav] (ナビドロワー用スライドイン暗幕オーバーレイ)
├── Overlay[all] (全パネルトグルオーバーレイ)
├── Overlay[user] (ユーザーモーダルオーバーレイ)
├── UserModal (Googleアカウント管理モーダル)
├── Search (オートコンプリートドロップダウン付き検索入力)
├── ToolBar (左サイドバーアイコンボタン)
├── TabBar (メインコンテンツパネル、タブ切り替え)
│ ├── Tab1Bar (保存リスト: リスト / ラベル付き サブタブ)
│ ├── Tab2Bar (検索履歴: 日付グループ化、チェックボックス選択)
│ ├── Tab3Bar (経路検索: 出発地/目的地入力、モード選択)
│ ├── Tab4Bar (デバッグ: Mercatorグリッドビジュアライザー)
│ └── Tab5Bar (リストエディタ: 保存リストの作成/編集)
├── TopBar (水平スクロール可能なトピックボタン)
└── TopicBar (スライドインナビゲーションドロワー)
```
### 状態管理
#### Atom システム
`atoms/utils.tsx`のカスタム atom 実装が軽量なリアクティブ状態を提供する。
各 atom は値とリスナー関数の Set を保持する。
値の設定により全リスナーが同期的に通知される。
```
Atom構造:
┌────────────┐
│ value: T │ ──→ プロパティアクセサ経由のget/set
│ set: Set │ ──→ Set<(next: T) => void> リスナー
└────────────┘
```
#### Render-Prop パターン
React コンポーネントはフックを直接使用する代わりに、`AtomValue`と`AtomValues`の render-prop コンポーネント経由で atom を消費する。
これにより再レンダリングが render-prop 境界に隔離される。
```tsx
// 単一atom