この記事は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_simulationssimulation_id(PK),title,sheet_size,thickness_mm,created_at,updated_at
origami_simulation_steps(simulation_id, step_index)複合PKstep_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-commitでbun 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だけ失敗し、元の例外を上書きしていた。
- Turso/libSQL クライアントの
- 修正:
replaceOrigamiSimulationとappendOrigamiSimulationStepsの手動トランザクション制御を廃止。client.batch(statements, "write")に統一し、libSQL 側のトランザクション管理に委譲。
- 効果:
- 履歴初期化(steps 空配列の置換)で
ROLLBACK失敗が発生しなくなり、DB更新が安定。
- 履歴初期化(steps 空配列の置換)で
##追加アップデート(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_stepsにpoint_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.jsontest:origamiスクリプトを追加。bun test src/lib/app/origami/simulation/*.test.js
.github/workflows/origami-regression.ymlpush(master)/pull_requestでbun 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件 passbun 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.mjspackage.jsonpatch:libsql-cloudflare追加postinstallでsetup-githooks後に自動実行
- 補正内容:
exports["."].import.nodeを./web.mjsへexports["."].require.nodeを./web.cjsへ
- Turso接続レイヤの import をルートから
- 効果:
bun run build:cfが完走し、OpenNext の worker 生成まで成功。- 同種の失敗がクリーン環境(CI/本番)でも再発しないよう、postinstall時に自動で補正が適用される。
##追加アップデート(2026-02-14 Cloudflareデプロイ失敗: Workerサイズ上限超過の修正)
- 症状:
build:cfは成功するがwrangler deployでcode: 10027が発生。- エラーは
Your Worker exceeded the size limit of 3 MiB。 - ビルド時点で
worker gzip estimateが無料枠上限(3145728 bytes)を超えていた。
- 原因:
- OpenNext の
default handlerに、静的生成だけで十分な処理や重い依存が含まれ、Worker本体へ不要に同梱されていた。 - 特に note系の実行時処理(本文同梱・API依存)と OGP用フォント同梱がサイズ増加要因だった。
- OpenNext の
- 修正:
- 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]のfallbackをblockingからfalseに変更し、ランタイム処理を削減。- Idle City OGPで同梱していた独自フォントを外し、標準フォント指定へ変更。
- 対象:
src/pages/api/og/idle-city.tsx
- 対象:
- スナップショット生成を用途別に分離。
src/lib/note/recommend-snapshot.jsonsrc/lib/note/feed-snapshot.json- 生成ロジック:
scripts/generate-content-snapshots.mjs
- note推薦APIを
- 効果:
- ローカル検証で
bun run build:cfのworker gzip estimateが3117847 bytesまで低下。 - 無料枠上限
3145728 bytesを下回り、サイズ超過エラーの再発条件を解消。
- ローカル検証で
##追加アップデート(2026-02-15 新規ID発行で reading 'has' エラーになる問題の修正)
- 症状:
- 「新しいIDを発行」実行時に
DBセッション作成に失敗しました: Cannot read properties of null (reading 'has')が発生。 - 発生点は
createSimulationのDBセッション作成処理。
- 「新しいIDを発行」実行時に
- 原因:
- Cloudflare Workers 実行時に
process.env参照が安定しないケースがあり、Turso接続設定の取得時に例外化していた。 - Turso接続実装が
process.env直読みに依存していた。
- Cloudflare Workers 実行時に
- 修正:
- Tursoクライアントの環境変数取得を
process.env直読みに固定せず、Cloudflare context (Symbol.for(\"__cloudflare-context__\")) からも取得できる実装に変更。 process.env参照はtry/catchで保護し、Workers環境での例外を握りつぶしてフォールバック。- Tursoクライアントの実装を
@libsql/client/webに統一。- 対象:
src/lib/turso/client.ts
- 対象:
- Tursoクライアントの環境変数取得を
- 検証:
bun run typecheck: passbun run build:cf: passworker gzip estimate:3129865 bytes(free limit3145728 bytes)