折り紙シミュレーション(第1段階)を /app/origami/simulation に追加

この記事はCodex製です。

##依頼内容と課題

  • https://ro1.dev/app/origami/simulation として、新規Webツールを追加する。
  • Tailwind / Shadcnui ベースで「折り紙シミュレーションができるツール」を実装する。
  • 折り操作をA点とB点の指定で定義し、ステップごとに状態を可視化する。
  • 各ステップをDB保存し、クエリパラメータと同期してブラウザ上で遷移可能にする。
  • 実現可能性の観点から、何が可能で何が未対応かを明確にする。

主な課題は、折り紙の「厳密な物理挙動」をWebブラウザで完全再現する難易度が高い点です。特に紙厚・接触・塑性・引き出し操作まで正確に扱うには、剛体/弾性体の高度なシミュレーションが必要になります。

##アプローチ

  • 第1段階として、計算量と実装コストを現実的に抑えつつ有効な検証ができる「幾何学ベース」モデルを採用。
  • 折り操作は A→B 一致(垂直二等分線で反転)として定義。
  • 折りごとに紙面ポリゴンを半平面クリッピングで分割し、移動側を反転して次状態を構築。
  • 山折り/谷折りでレイヤー順序を更新し、厚みは「レイヤー数 × 紙厚(mm)」で近似表示。
  • /api/app/origami/simulation を追加し、Turso(libSQL)へステップ履歴を保存。
  • URLクエリ id / step を同期し、任意ステップへ遷移できるようにした。
  • 折り筋だけを先に追加できるステップ種別を追加し、後で折る工程に分離できるようにした。
  • 1ステップ展開 / 完全展開をステップとして追加できるようにした。
  • 既存の折り筋から自動で折りたたむ機能を追加し、より現実的な工程を再現できるようにした。
  • Fold View のクリック座標取得をSVG座標変換に変更し、クリック位置とA/B点のずれを解消した。
  • UIラベルは原則日本語へ統一した。

##アウトプット

  • 追加: src/pages/app/origami/simulation/index.tsx
  • 追加: src/pages/api/app/origami/simulation/index.ts
  • 追加: src/lib/app/origami/simulation/engine.ts
  • 追加: src/lib/turso/client.ts
  • 追加: src/lib/turso/origami-simulation-repository.ts
  • 追加: src/sql/turso/create_origami_simulation_tables.sql
  • 更新: src/pages/app/index.tsx
  • 追加: src/_memo/content/2026/02/origami-simulation-phase1.mdx

実装した主機能:

  • A点/B点指定による折り操作(山折り/谷折り)
  • 折り筋のみステップ(先に折り筋を付ける工程)
  • 1ステップ展開 / 完全展開
  • 既存折り筋に沿った自動折りたたみ
  • 折りステップの可視化とステップ遷移
  • 紙厚近似を含むレイヤー可視化
  • 蛇腹の一括ステップ生成
  • ステップ履歴のDB保存(Turso)
  • id / step のURL同期
  • クリック位置とA/B点の位置一致
  • 日本語UIへの統一

Tursoテーブル定義(主要):

  • origami_simulations
    • simulation_id (PK), title, sheet_size, thickness_mm, created_at, updated_at
  • origami_simulation_steps
    • (simulation_id, step_index) 複合PK
    • step_id, step_kind, fold_type, point_ax, point_ay, point_bx, point_by, label, created_at

##UI文言ガード(追加)

  • 要望に合わせて、Phase 1 のようなアドリブタグ表現をUIから削除。
  • pre-commitで禁止文言を検知してコミットをブロックする仕組みを追加。
  • 設定ファイル:
    • config/codex-copy-guard.json
  • 検査スクリプト:
    • scripts/guard-ui-copy.ts
  • フック:
    • .githooks/pre-commitbun run guard:copy-style を必須実行

これにより、Codex側が同種のタグ表現を再度差し込んだ場合、コミット時に自動検知して止められる運用にした。

第1段階の制約:

  • 紙厚の厳密接触判定・衝突解消は未実装
  • しわ、伸縮、塑性変形は未実装
  • 蛇腹後の引き出し操作などの力学的再現は未実装
  • 龍神級の超高密度モデルには、追加の計算モデル最適化が必要

##追加アップデート(2026-02-14 UI改善)

  • Fold View の名称を維持し、左カラムの表示をスクロール追従(固定表示)に調整。
  • 固定表示は lg 以上の2カラム時に有効化し、スマホ縦表示では操作性を優先して通常表示に切り替え。
  • ヘッダーの /app に戻る 導線を削除。
  • モダンダッシュボード風に、背景・カード・余白・影のスタイルを再設計。
  • iPhone SE 幅を含む小画面で崩れにくいよう、余白・見出しサイズ・グリッド分割を調整。
  • Fold View 内でマウス移動中の座標表示を追加。
    • x / y の実座標
    • 左右上下それぞれの位置比率(%)
    • 十字ガイド線による位置可視化

##追加アップデート(2026-02-14 DB初期化エラー修正)

  • 症状:
    • 履歴初期化時に SQLITE_UNKNOWN: SQLite error: cannot rollback - no transaction is active が発生。
  • 原因:
    • Turso/libSQL クライアントの execute() は文ごとに独立接続で実行されるため、BEGIN/COMMIT/ROLLBACK を個別 execute() で管理すると同一トランザクションとして保証されない。
    • エラー時に ROLLBACK だけ失敗し、元の例外を上書きしていた。
  • 修正:
    • replaceOrigamiSimulationappendOrigamiSimulationSteps の手動トランザクション制御を廃止。
    • client.batch(statements, "write") に統一し、libSQL 側のトランザクション管理に委譲。
  • 効果:
    • 履歴初期化(steps 空配列の置換)で ROLLBACK 失敗が発生しなくなり、DB更新が安定。

##追加アップデート(2026-02-14 折り筋指定折り / 被せ折り対応)

  • 折り筋表示の改善:
    • Fold View の折り筋を紙レイヤーより前面に描画。
    • 一度付けた折り筋を常時視認できるようにした。
    • 折り筋番号を表示し、クリックで選択できるようにした。
  • 任意折り筋での折り:
    • 折り筋一覧から任意の1本を選択して「その折り筋で折る」操作を追加。
    • 折り済み折り筋は状態表示し、再適用を抑制。
    • 現在状態で折り可能/不可を判定し、不可時は理由を表示。
  • 被せ折り(2ステップ):
    • 2本の折り筋を選択し、連続2手としてステップ追加する操作を追加。
    • 2本が交差しない場合は実行を拒否。
    • 1手目・2手目それぞれで折り可否を判定し、成立しない場合はエラー表示。
  • 自動折りたたみの改善:
    • 折れない折り筋件数を除外数として明示し、結果メッセージに反映。

##追加アップデート(2026-02-14 局所フラップ折りの導入)

  • 機能分解の再整理:
    • fold: A/B一致による点一致折り(従来)
    • crease: 折り筋のみ付与
    • crease_fold: 折り筋A-B + 対象点(C/D)で「どの側のフラップを動かすか」を明示する局所折り
    • unfold_one / unfold_all: 展開操作
  • エンジン改善:
    • crease_fold を新しい正式ステップとして追加。
    • 折り筋線そのものを軸に反転し、対象点を含むフラップ層のみを移動対象にする方式へ拡張。
    • 対象点が折り筋上、または対象フラップ未検出のケースを明示的に不可判定。
    • 一部フラップだけが折り筋と交差する場合も、不可部分を警告しつつ可動部分のみ適用。
  • UI改善:
    • C点/D点を導入し、クリック配置・数値入力・ビュー上マーカー表示に対応。
    • 任意折り筋に対して「C点で局所折り」「自動推定で局所折り」を実行可能にした。
    • 被せ折りは C点(1手目)+ D点(2手目)を使う2ステップ操作に更新。
  • 自動推定:
    • 選択折り筋の法線方向に候補点群を生成し、成立する候補を探索して局所折りを実行。
  • DB/API対応:
    • step_kind = crease_fold を保存/復元できるよう拡張。
    • origami_simulation_stepspoint_cx, point_cy を追加し、局所折りの対象点を永続化。

##追加アップデート(2026-02-14 自動回帰テストとCI)

  • 目的:

    • 機能追加時に「実際に折れるか」を機械的に判定できるようにし、CIで継続的に品質監視する。
  • 追加:

    • src/lib/app/origami/simulation/regression-cases.ts
      • 回帰ケース実行ロジックを追加。
      • runYakkoSanRegressionCase()
        • やっこさん基礎工程(角・辺の中心合わせ)を順次適用し、成立判定。
      • runTraditionalCraneRegressionCase()
        • 伝統折り鶴の予備折り → つぶし折り探索 → 被せ折り探索を段階判定し、成立可否を返却。
        • 回帰期待値 expectedFoldable を設定し、将来の退行を検知。
      • runOrigamiRegressionCases()
        • CI/UTで共通利用するケース一覧を返却。
    • src/lib/app/origami/simulation/origami-regression.test.js
      • Bun Test で回帰ケースを実行。
      • 各ケースで「期待可否」と一致するかを検証。
      • 失敗時診断用に message / metrics が必ず返ることを検証。
    • package.json
      • test:origami スクリプトを追加。
        • bun test src/lib/app/origami/simulation/*.test.js
    • .github/workflows/origami-regression.yml
      • push(master) / pull_requestbun run test:origami を自動実行。
  • 効果:

    • 少なくとも「やっこさん基礎工程」と「伝統鶴の主要工程探索」の回帰判定がCIで常時可視化される。
    • 機能変更で折り成立性が変化した場合、PR段階で早期検知できる。

##追加アップデート(2026-02-14 折り再現解像度の改善)

  • 目的:

    • 「対象点が少しズレると折れない」「折り筋が境界と重なると折れない」という失敗要因をエンジン側で吸収し、実際の折り工程再現性を上げる。
  • 追加:

    • src/lib/app/origami/simulation/engine.ts
      • 対象フラップの解決を強化。
        • 点内包判定に失敗した場合、ポリゴン境界までの最近傍距離で対象層を補足。
        • 近傍採用時は警告メッセージを返して可観測化。
      • 境界ヒンジ回転を追加。
        • 折り筋が対象層の境界に重なるケースで、層全体をヒンジ回転できるように拡張。
        • 従来は staticArea=0 として失敗していたケースを成立扱いに変更。
    • src/lib/app/origami/simulation/solver.ts
      • 折り対象点探索ロジックを新規分離。
        • estimateAutoFoldTarget()
        • resolveCreaseFoldTarget()
        • buildAutoTargetCandidates()
      • UIと回帰検証の探索ロジックを共通化し、挙動差分を削減。
    • src/pages/app/origami/simulation/index.tsx
      • 既存の探索ロジック重複を削除し、solver.ts 経由へ統一。
    • src/lib/app/origami/simulation/engine-crease-fold.test.js
      • 最近傍対象補足と境界ヒンジ回転のユニットテストを追加。
  • 検証結果:

    • bun run test:origami: 7件 pass
    • bun run typecheck: pass
    • 伝統折り鶴回帰ケースは expectedFoldable=true で合格(現探索条件内で主要工程完走)。

##追加アップデート(2026-02-14 Cloudflare本番ビルド失敗の修正)

  • 症状:
    • bun run build:cf の OpenNext バンドル段階で以下が発生。
      • Could not resolve "@libsql/client"
      • Could not resolve "@libsql/isomorphic-ws"
    • どちらも workerd 条件が web.* を指す一方で、OpenNext 側のテンプレートコピーで該当ファイルが欠落した状態になり、esbuild 解決に失敗していた。
  • 修正:
    • Turso接続レイヤの import をルートから http サブパスへ変更。
      • src/lib/turso/client.ts
      • @libsql/client@libsql/client/http
    • インストール直後に @libsql/isomorphic-ws の export 条件をCloudflare向けに補正する自動パッチを追加。
      • scripts/patch-libsql-cloudflare.mjs
      • package.json
        • patch:libsql-cloudflare 追加
        • postinstallsetup-githooks 後に自動実行
      • 補正内容:
        • exports["."].import.node./web.mjs
        • exports["."].require.node./web.cjs
  • 効果:
    • bun run build:cf が完走し、OpenNext の worker 生成まで成功。
    • 同種の失敗がクリーン環境(CI/本番)でも再発しないよう、postinstall時に自動で補正が適用される。

##追加アップデート(2026-02-14 Cloudflareデプロイ失敗: Workerサイズ上限超過の修正)

  • 症状:
    • build:cf は成功するが wrangler deploycode: 10027 が発生。
    • エラーは Your Worker exceeded the size limit of 3 MiB
    • ビルド時点で worker gzip estimate が無料枠上限(3145728 bytes)を超えていた。
  • 原因:
    • OpenNext の default handler に、静的生成だけで十分な処理や重い依存が含まれ、Worker本体へ不要に同梱されていた。
    • 特に note系の実行時処理(本文同梱・API依存)と OGP用フォント同梱がサイズ増加要因だった。
  • 修正:
    • note推薦APIを @/lib/note/api 依存から切り離し、軽量な src/lib/note/recommend-snapshot.json 読み込みへ変更。
      • 対象: src/pages/api/note/recommend.tsx
    • note RSS も @/lib/note/api 依存を外し、src/lib/note/feed-snapshot.json からXML生成する方式へ変更。
      • 対象: src/pages/note/rss.tsx
    • src/lib/note/snapshot.json から本文を除外し、メタ中心へ軽量化。
    • noteページのトップレベルimportを動的importへ変更し、サーバーバンドルへの巻き込みを抑制。
      • 対象: src/pages/note/[slug].tsx, src/pages/note/index.tsx
    • note/[slug]fallbackblocking から false に変更し、ランタイム処理を削減。
    • Idle City OGPで同梱していた独自フォントを外し、標準フォント指定へ変更。
      • 対象: src/pages/api/og/idle-city.tsx
    • スナップショット生成を用途別に分離。
      • src/lib/note/recommend-snapshot.json
      • src/lib/note/feed-snapshot.json
      • 生成ロジック: scripts/generate-content-snapshots.mjs
  • 効果:
    • ローカル検証で bun run build:cfworker gzip estimate3117847 bytes まで低下。
    • 無料枠上限 3145728 bytes を下回り、サイズ超過エラーの再発条件を解消。

##追加アップデート(2026-02-15 新規ID発行で reading 'has' エラーになる問題の修正)

  • 症状:
    • 「新しいIDを発行」実行時に DBセッション作成に失敗しました: Cannot read properties of null (reading 'has') が発生。
    • 発生点は createSimulation のDBセッション作成処理。
  • 原因:
    • Cloudflare Workers 実行時に process.env 参照が安定しないケースがあり、Turso接続設定の取得時に例外化していた。
    • Turso接続実装が process.env 直読みに依存していた。
  • 修正:
    • Tursoクライアントの環境変数取得を process.env 直読みに固定せず、Cloudflare context (Symbol.for(\"__cloudflare-context__\")) からも取得できる実装に変更。
    • process.env 参照は try/catch で保護し、Workers環境での例外を握りつぶしてフォールバック。
    • Tursoクライアントの実装を @libsql/client/web に統一。
      • 対象: src/lib/turso/client.ts
  • 検証:
    • bun run typecheck: pass
    • bun run build:cf: pass
    • worker gzip estimate: 3129865 bytes(free limit 3145728 bytes