fullseye

Fullseye Script / Runtime — 要件定義と基本設計(確定案)

発端 = ユーザーの問い(2026-08-15): 「Python で進めて実用性としては問題ないか? 製造業ではまだ Python でシステムを組むというのは少ない傾向にあるよ。

本書はこの問いに、憶測でなく一次情報と実測で答え、要件定義と基本設計を確定する。 実測の生データ = docs/FSCRIPT_MEASUREMENTS.md / 言語仕様の正本 = docs/FSCRIPT_LANGUAGE.md


0. 問いの再定義 — 「Python か否か」は問いとして誤りだった

調査の結果、この問いは 3 つの別々の問いが 1 語に潰れていたことが分かった。分けると答えが変わる。

潰れていた問い 答え 根拠
(a) 設計環境(IDE)を Python で作ってよいか 問題なし 顧客の設備上で動くものではない。HALCON の HDevelop も重量級 GUI
(b) 検査ロジックを走らせる言語処理系が Python でよいか 問題なし(コストがノイズに埋没して検出できない) 実測 §1.4 / HALCON 自身が本番にインタプリタを出荷(§1.3)
(c) 画像処理エンジンの実体が Python/numpy でよいか ここだけが本当の争点。現状のままでは No 実測 §1.2(カーネルで 23 倍・オブジェクトモデルで 5.8 倍)

業界が受容しているのは (a)(b) であり、拒否されているのは言語ではなく「保証できないランタイム」。 そして Fullseye が現在いるのは (c) — 前例が確認できない場所である。ここが結論のすべて。

★本書の最終形(先に結論)

  1. Python で進めてよい。 争点は L1 だけで、解は「L1 の契約をバックエンド差し替え可能に切る」(判断 1)。
  2. 実行モデルは VM 一本、codegen 配布はやらない(判断 2)。
  3. ★言語を独自 DSL にするか普通の Python にするかは、いま決めない(判断 3・改訂)。 4 案を 2 レンズで独立採点したところ評価が割れ、決定的な情報は技術側でなく顧客側にあると 両者が独立に結論した。そして最初の約 6 週間の作業は、どちらを選んでも同一である。 → 共通核(フェーズ I)を先に作り、顧客への 2 問で分岐する。
  4. 最重要の実装課題は速度ではなく fail-open の是正(§1.6b)と意味論欠陥 5 件の封鎖(§1.6)。

1. 判断に使った証拠

1.1 商用プラットフォームは Python を第一級 API として本番提供している(一次情報)

外部 AI(Codex, web 検索つき read-only)に一次資料を当てさせ、公式ドキュメントで裏を取った結果:

製品 公式 Python API 位置づけ
MVTec HALCON / HDevEngine あり 第一級(試作専用ではない)
Euresys Open eVision あり 第一級
Basler pylon (pypylon) あり 正式 API(ただしサポートは限定的と自ら明記)
Cognex VisionPro / In-Sight 確認できず .NET 中心
MERLIC / NI VDM / Keyence CV-X / OMRON FH 確認できず 専用 GUI・専用コントローラ

★ただし決定的な但し書き: これらの Python API はすべて「Python → ネイティブライブラリのバインディング」であり、 画像演算を Python バイトコードで実行する構成ではない。 → 業界が認めているのは「Python がネイティブエンジンを駆動する」形であって、 「エンジン自体が Python」ではない。この区別が本件の核心。

1.2 「Python だから弾かれる」という一般条件は存在しない

Codex が一次資料で確認した結論(確信度: 高): 「Python 製だから採用可能/不可能」という製造業共通の基準や採用率は確認できない。 弾かれるのは以下の個別要件違反であり、そのすべてが Python 非依存である:

含意: 障壁は言語ではなく「保証・証拠・供給」。これは Python でも C++ でも同じ量の仕事であり、 むしろ一人ベンダーであることの方が本質的リスク(Codex も同じ結論)。 なお「日本では SIer 文化ゆえ Python が拒否される」「装置保守年数は必ず 15 年」といった一般化は、 公的統計では裏付けられなかった(IPA 2012 年組込み調査で C 60.3% / C++・C# 21.0% という古いデータのみ)。

★敵対検証が潰した「Python 不利」論(いずれも Python 固有ではなかった):

よく聞く反論 検証の結論
Cognex / Keyence CV-X / OMRON FH に Python の入口が無い C++ / C# にも無い。これらは専用 GUI・独自マクロ・専用コントローラであり、真の分岐は「専用コントローラを買うか PC ベースで作るか」= 言語選択と直交
CPython は 5 年 EOL で装置寿命とズレる 比較先の .NET の方が短い(現行 .NET は LTS 3 年 / STS 2 年)。同じ論法なら C# の方がズレは大きい
Euresys 公式が「性能が要る箇所は C++/C# で書き直す前提」と明記 一次情報の誤読。原文は “programming a specific functionality in more runtime efficient languages such as C++/C# is still possible” = 選択肢の提示であって前提ではない
PySide6 の QImage(numpy_buffer) は use-after-free になる Qt が全言語に課している API 契約。Qt 公式は C++ の同一コンストラクタにも同じ寿命要件を明記しており、Python 固有のコストではない
凍結配布が 345 MB で C# の単一 exe(数十 MB)より 10-50 倍重い 成果物の中身が違う。345.9 MB の 94.9% は OpenCV / Qt / OpenBLAS などのネイティブバイナリで、CPython 本体は 2.8%。同機能を C# で作れば同じネイティブを積む

1.3 ★HALCON は本番にインタプリタを出荷している(MVTec 公式で確認)

HDevEngine is “an interpreter-based library that loads and executes HDevelop programs and procedures at runtime” — 対応言語 C++, C#, Python, .NET。 C++/C# への export は「HDevEngine API 呼び出しのラッパ生成」であり、HDevelop のロジックをネイティブへ変換しない。

含意(設計を 1 段単純化する): 世界最大手が本番でインタプリタを走らせて成立している。速いのはオペレータがネイティブだから。 → 「IDE = VM / 配布 = compile」の二モードは不要。 → Codex が Path D の致命傷と指摘した「インタプリタと codegen の意味論を 650 op で二重保守」を、そもそも背負わない

1.4 実測(この PC。詳細 = docs/FSCRIPT_MEASUREMENTS.md)

4 MP(2048x2048)・200 blob の検査 1 サイクル:

  p50 対現状
現状(fscript.py L1 + AST インタプリタ) 1448 ms 1.0x
言語層を外して L1 を直接呼ぶ 1382 ms 1.05x
ObjectSet(ラベル画像 + ID 列)へ置換 250 ms 5.8x
ネイティブカーネル(OpenCV)+ ObjectSet 10.8 ms 134x

この 134 倍のどこにも「Python をやめる」は入っていない。

1.5 コードの実態(実測で判明した、設計書との食い違い)

設計書の記述 実際
「実装済み種 = fslib.py fslib.py は存在しない。builtin は fscript.py 内。L1/L2 未分離
for Obj in Objects 未実装(_KEYWORDSin が無い)
「smoke test 実証」 コミットされたテストは 0 件だった(本セッションで新規作成: 22 passed / 5 xfail)
「C codegen 資産の延長で DLL 化」 imgops.c = 112 行・8 op(650 中 1.2%)。しかも全て image→image フィルタ系で region/blob 系はゼロ = 実測で 21 倍差が出た側を 1 つもカバーしていない
バックエンド選択機構が無いgaussiancv_gaussian別の op として登録 = 「同じスクリプトを別実装で走らせる」が表現できない

既存資産 difftest.py の実態(★自己訂正): 当初本書は「Python 実装を正解オラクルとし C 実装を差分テストで検証する機構が既にあるので、 ネイティブ移行の検証ハーネスは新規開発不要」と書いた。これは寛容すぎた。 敵対検証の指摘を受けて一次確認した結果:

主張 実態
「差分テストの機構が実在」 考え方としては実在(141 行)。Python 側は毎回走る
「規模だけが未証明」 C 側は一度も実行されたことがない。記録 6 件すべて status: skipped(理由 = 「champion が C ランタミムに無い op を使う」or「C ツールチェーンが無い」)。この機械に gcc/clang も無い
「op 単位で検証できる」 できない。比較単位は champion パイプライン全体であって op 個別ではない
「合否判定がある」 許容差は c_max < 1e-3ハードコード 1 本。op ごとの契約になっていない

正しい言い方: 「Python をオラクルにする」発想は既存だが、 本設計が必要とする per-op 差分ゲートは存在しない。 ただし 本セッションの tests/test_fslib.py で、それが実際に動くことは実証した (gauss 3σ / connection / region_features / select_shape が numpy 実装と一致、宣言された許容差つき)。 → ハーネスは「拡張」ではなく「新規」だが、PoC で成立は確認済み(§1.7)。

1.6 実測の過程で見つかった「黙って誤答する」欠陥 5 件

すべて根因は同じ = 言語が自前の型モデルを持たず、numpy/Python の意味論を継承している実装言語を変えても残る。回帰テスト = tests/test_fscript.py

★I-2 で 5 件すべて封鎖済(2026-08-15, commit は下記)。方法 = fscriptfslib の型モデルへ載せ替えた: iconic 値を FImage/Region/ObjectSet(sort を型が運ぶ・内容から推測しない・真偽値を持たない)にし、 制御タプルの + を HALCON 準拠の要素ごと演算に、連結は [t1, t2](flatten)にした。 画素 op は fslib に委譲(実装の二重化なし)。全スイート 4372 → 4377 passed / 0 xfailed(回帰なし)。

# 欠陥 症状 状態
1 * を行途中でもコメント扱い A := I * 2A := I に。エラーも出ない 修正済
2 _norm01 が画像の最大値で正規化 隅の明るい画素 1 個で同じ部品の area が 256 → 1 修正済(I-2)FImage が dtype/sensor 由来の値域を運ぶ。範囲外の正反射は判定を変えない
3 数値 Tuple の + が連結 HALCON は要素和 [11,22,33]、現状は連結 修正済(I-2)+ を要素ごとに。連結は [t1, t2]
4 iconic が条件式で暗黙に真偽化 if (Region).any() で通る 修正済(I-2) — iconic は真偽値なし → FScriptError
5 比較が配列を返し条件で潰れる if (Image = 0) が「1 画素でも 0 なら真」 修正済(I-2) — iconic の比較は FScriptError(明示 reduction を要求)

欠陥 2 が最も重かった: 実ラインの正反射・ホットピクセルで判定が黙って反転していた。 これは性能問題ではなく信頼性問題であり、ネイティブ化より優先度が高いと判断し、I-2 で最初に閉じた。

1.6b ★★最重要 — 650 op レジストリは fail-open である(敵対検証の指摘から発見)

外部の敵対検証が「片方の依存を外すと op は登録されたまま黙って benign 値を返す」と指摘したので、コードで確認した。事実だった。

backends.py_safe全 op をこう包んでいる:

def w(v, a, b):
    try:
        out = fn(v, a, b)
    except Exception:
        out = None                 # ← あらゆる例外を飲む
    return sanitize(out, v, out_sort)

sanitize(None, ...)fallback() を呼び、「宣言された sort として妥当な無害値」を返す。 実測で確認:

out_sort 例外時に返る値 検査における意味
region 全ゼロの region 「欠陥ゼロ」
feature 0.0 測定値ゼロ
image 入力をクリップしたもの 処理されなかった画像

本番検査で op が何らかの理由で例外を投げると(依存欠落・退化入力・バグ)、 パイプラインは黙って「欠陥なし」を返し、全数が OK 判定になる。 製造検査として最悪の失敗様式であり、判定そのものが fail-open になっている。

★ただしこれは進化エンジンには正しい設計である(失敗候補は低スコアで淘汰されるべきで、 探索全体を落としてはいけない)。同じ挙動が Runtime では致命的というだけ。

★ただし「654 op のうち N 個は死んでいる」ではない(自分で監査した): 外部証拠は「ライブラリ欠損時は eval が例外 → sanitize で無害値に degrade するため、 op 数には数えられるが黙って何もしない可能性がある」と述べていた。 そこで 650 op すべてを、宣言された in_sort に合わせた入力で実行して監査した:

結果 件数
正常に出力 634
例外が呼び出し側へ出る 17
恒等 / 全ゼロ / 0.0 20

そして「何もしない」20 件の大半は、この入力に対して正しく恒等またはゼロだった (identity / abs_image(入力非負)/ it_full_domain(既に全面)/ zoom_region(knob 0.5 = 変換なし)/ ncc_locate(テンプレート未指定)/ cv_hough_lines(画像に直線が無い)等)。 → 依存が全て揃ったこの機械では、死んでいる op の証拠は無い。 危険なのは依存が欠けた機械であり、そこでは op が登録されたまま黙って劣化する。

要件(R4 に追加): Runtime は起動時に、ロードするレシピが使う全 op について 「登録されているか」ではなく「動作するバックエンドが実在するか」を検証し、 1 つでも欠ければ READY にならない(self-check)。名前の存在をもって可用と見なさない。

★実装済(2026-08-15, I-2 後続, commit f1a7379)= この self-check プリミティブを fslib に追加: unmet_ops(op_names, profile)(profile を満たすバックエンドが無い op を列挙=unregistered も unmet)/ readiness_report(op_names, profile)(各 op が dispatch するバックエンド、無ければ None)/ require_ready(op_names, profile)(1 つでも unmet なら FsBackendError で起動拒否・全列挙、degrade しない)。 industrial は numpy fallback を持たないので numpy-only op は unmet=起動拒否、studio は numpy へ degrade (tests/test_fslib.py に 5 テスト、cv2 非依存)。

★Runtime ローダーへ配線済(2026-08-16, commit 9b74fd3)= fsruntime.py: compile_recipe(recipe, profile) が fail-closed で ①ABI major 一致 ②source SHA-256 が manifest 署名と一致(改ざん/drift 検出) ③require_ready で backend self-check ④golden vector で「以前と同じ判定」を再現(R5) を検査し、いずれか失敗で FsNotReady(degrade しない)。 golden 実行が backend self-check と判定証明を兼ねる。Recipe/GoldenVector/ReadyRecipe/sign + 常駐 FullseyeRuntime(Verdict OK/NG/ERROR/TIMEOUT)。

★敵対レビューで fail-closed の穴を摘発・修正済(2026-08-16, 4 レンズ WF 21 findings→一次検証、tests/test_fsruntime.py 24 件): (F1)industrial は 650op fail-open レジストリの long-tail op を拒否(curated builtin 以外は起動拒否。従来は require_ready 素通り + api.RT_safe が benign「欠陥なし」を返し inspect が “ok”=まさに §1.6b の穴が産線に到達)。 (F2)industrial は golden ≥1 必須(判定証明ゼロで READY を防ぐ)。(F3)industrial は署名必須(空 source_sha256 のスキップを塞ぐ)。 (F4)空 expect の golden を拒否。(F5)golden 照合の NaN fail-open を修正(NaN は常に照合をすり抜けていた)。(F6)照合の生例外を FsNotReady に統一。(F7)np scalar 期待値も tol 分岐。 (F8)area_center→measure_all の誤マップ除去(純 numpy で dispatch せず cv2 不在機を誤拒否)。(F9)inspect は想定外例外も Verdict(“error”)。(F10)digest を 全 manifest(source+abi+build_id+goldens) へ拡張。 ★second-pass 検証(2026-08-16, 修正を再攻撃した 4 レンズ WF 25 findings→一次検証)で残存バイパスを追加修正: (G1)tol=inf/nan/負 を GoldenVector 構築時に拒否(tol=inf は golden を無効化していた)/ (G2)registry op 拒否を全プロファイルへ (studio/reference 経由の fail-open 露出を封鎖)/ (G3)bool 分岐は両方 bool 必須(bool↔数値の取り違え)/ (G4)inputs/expect を dict 検証 / (G5)golden 実行の生例外を FsNotReady へ / (G6)deadline_ms を有限正数に検証(NaN が timeout を無効化していた)/ (G7)0-d 配列を scalar 化 / (G8)照合を配列対称化。 tests/test_fsruntime.py 24→39 件。★F1/F2/F3 の核は再攻撃でも破れず(honest 負の確認)。 ★正直な限界: 署名は完全性/drift 検出であり暗号的真正性ではない(無鍵 SHA-256=Recipe を編集できる者は再署名可)。真の provenance は鍵付き署名=配布時の課題。native hang は同スレッドから中断不可(watchdog が要る)。digest は golden の input 画素を含まない(既知・documented)。 残り = 実 CLI/常駐配布イメージ結線(PLC プロトコル・OS コア確保・§4.1 二プロファイル)+ 鍵付き署名

これが「Studio と Runtime は意味論を分けねばならない」の最も具体的な証拠であり、 §4.2 の industrial = fail-closed 設計の必然性を示す。 要件 R4 に「Runtime プロファイルは op の例外を決して飲まない」を明記する(下記)。 対比は tests/test_fslib.py::test_the_evolution_registry_is_fail_open_and_fslib_must_not_be で固定。

1.7 ★確定案の要石を PoC で de-risk 済み(本セッションで実装・実測)

本確定案は「1 op = N バックエンド + Python 実装をオラクルとする差分テスト」に全体重を預けている。 成立しなければ増分 2 以降が崩れるので、先に最小 PoC を作って確かめた

実装 = fslib.py(L1 の種)/ 契約テスト = tests/test_fslib.py(18 passed)。

証明できたこと:

主張 証拠
型が sort と値域を運ぶ(推測しない) ホットピクセルを足しても測定部品の面積は 256 のまま。同じ入力を現 fscript に通すと変わる(テストで両方を実行して対比)
iconic は真偽値を持たない bool(FImage/Region/ObjectSet)FsTypeErrorif (Region) が書けない
誤った sort を渡すと型エラー connection(image) / gauss(region)FsTypeError
1 op = N backend、プロファイルで選択 gauss 1 つに numpy と cv2 の実装。with profile("industrial") で切替
産業プロファイルは黙って劣化しない ネイティブ実装が無い op は FsBackendError(fail-closed)。「配布したら遅かった」を構造的に防ぐ
ネイティブはオラクルと一致することを証明してから採用 差分テスト: gauss(3 σ)/ connection / region_features / select_shape が numpy 実装と一致
ObjectSet はマスクを作らない ラベル画像 1 枚 + id 列。select_shape は id フィルタのみでラベル画像を共有(コピーなし)

性能(同じスクリプト・同じ結果・プロファイル切替のみ):

  studio(numpy オラクル) industrial(native) 倍率
1024x1024 / 50 blob 58.6 ms 5.42 ms 10.8x
2048x2048 / 200 blob 240.2 ms 16.94 ms 14.2x

PoC 構築中に見つかった設計要件(実測駆動): 最初の版は 4 MP で 41.7 ms だった。 原因は API 形状が連結成分パスを 3 回走らせていたこと(connection で 1 回、select_shape の測定で 2 回目、 最終取得で 3 回目)。→ ObjectSet は測定済み特徴量を保持して運ぶことを要件化し(R1 に反映)、 41.7 → 16.9 ms。「オブジェクトモデルは形だけでなく、測定結果の持ち回りまで含めて設計する」という学び。

正直な残差: 手書き cv2 の 10.8 ms に対し PoC は 16.9 ms。差の原因は特定済み (threshold が u8 上で numpy 比較 2 回 + AND、connection で uint8 への変換コピー)。 アーキテクチャの限界ではなく、実装の詰めしろ。

1.8 産業プロファイル候補 44 op の native カバレッジ = 44/44

増分 2 の kill criteria(「頻出 op の半分以上にネイティブの道が無ければ設計を捨てる」)を先に検証した。 実検査で頻出する 44 op を列挙し、OpenCV に対応関数が存在するかを確認 → 不足 0

gauss / box / median / bilateral / sobel / laplace / canny / threshold / otsu / adaptive_threshold / dilation / erosion / opening・closing / connection+features / contours / contour_features / min_area_rect / fit_circle / fit_line / fit_ellipse / convex_hull / moments / hu_moments / match_template / warp_affine / warp_perspective / resize / remap / calibrate / solve_pnp / hough_lines / hough_circles / distance_transform / watershed / histogram / equalize / inrange / cvt_color / subpixel_corner / optical_flow / barcode / qrcode / phase_correlate / ecc_align

産業プロファイルの到達に Rust/C の新規実装は不要。Path C(最初からネイティブコア)を採る理由がさらに薄れる。

★「名前があること」から「実際に動くこと」へ格上げ済み: 代表 16 op を 4 MP(2048x2048)の実 u8 フレームで実行し、 16/16 成功。p50(ms):

op ms op ms op ms
GaussianBlur 2.09 connCompWithStats 8.77 distanceTransform 6.59
medianBlur 6.58 findContours 2.64 HoughLinesP 12.52
Canny 3.93 minAreaRect / fitEllipse / moments ~0.01 solvePnP 0.03
adaptiveThreshold 8.99 warpAffine 2.46 cornerSubPix 0.01
morphologyEx(open) 1.43 matchTemplate 46.74    

matchTemplate が突出して遅い(46.7 ms) — しかもこれは回転・スケール不変ですらない NCC。 つまり マッチングは機能面で最も弱く、同時に最も高価。 → 下表の「差別化 3 領域」のうち shape-based matching が最優先であることが、数字でも裏付けられた。

★ただし正直に区別すべきこと — 問題は「ネイティブカーネルが無い」ことではなく 「OpenCV のアルゴリズム集合 ≠ HALCON のアルゴリズム集合」である。HALCON が高価な理由そのものが OpenCV に無い:

HALCON の中核ツール OpenCV の対応 実態
shape-based matching(回転・スケール不変、サブピクセル、遮蔽耐性) matchTemplate NCC テンプレートマッチのみ。回転・スケール不変ではない = 別物
XLD サブピクセル輪郭 + 計測 findContours 画素レベル。サブピクセル輪郭の当てはめ・計測は無い
1D measure object(measure_pairs 等のエッジ対計測) 相当機能なし

含意(戦略): cv2 で「速い産業プロファイル」はすぐ届く。 一方 HALCON 級の差別化はこの 3 つにあり、そこは自前実装が要る。 そしてそこは Fullseye の進化エンジンが新規性を出せる可能性がある唯一の領域でもある (op を 650 個並べることではなく、この 3 領域で「設計された」アルゴリズムを出すこと)。 ロードマップ上は 増分 2 = cv2 で産業成立 → 増分 5 = 差別化 3 領域を自前実装、と位置づける。


2. 確定判断

判断 1 — Python で進める。ただし「L1 を numpy/scipy の唯一実装に固定しない」ことを条件とする

実測が示す優先順位は 型・意味論 → オブジェクトモデル → 画素カーネル → 言語の実行方式。 言語の実行方式は最も影響が小さい。∴ 言語処理系・IDE・進化エンジンは Python のままで正しい。 争点は L1 だけであり、その解は「言語を替える」ではなく「L1 の契約をバックエンド差し替え可能に切る」。

判断 2 — 実行モデルは VM 一本。codegen による配布はやらない

MVTec が本番でインタプリタを出荷している(§1.3)ことと、言語層のコストが検出できない実測(§1.4)から、 二モード(VM + compile)は要件ではない。同じ VM を Studio と Runtime の両方で使う。 codegen は将来の任意最適化であって、設計の柱にしない。→ 意味論の二重保守という最大の負債を回避。

★HALCON 自身が二実行系の代償を払っている(調査で判明した強力な傍証):

★反例を無視していないこと: 市場には両方のモデルが実在する。 Zebra Aurora Vision Studio(旧 Adaptive Vision)は Studio から C++ コード/プロジェクトを生成する構成を持つ (配布は Executor/Runtime または生成アプリ)。つまり codegen 配布は「誰もやっていない」わけではない。 それでも HALCON 型を選ぶ理由は 3 つ: (1) 一人開発で 650 op の意味論を 2 系統維持できない(Codex 指摘・確信度高)、 (2) 実測上 codegen の性能動機が無い(言語層のコストが検出できない)、 (3) ウォッチ/step という本製品の差別化が生きた変数環境を要求し、VM 側に重心がある。 → 顧客が「C#/C++ アプリに組み込みたい」と実際に言ってきた場合は、codegen ではなく C ABI の Runtime DLL + 薄い .NET ラッパで応える(§6 kill criteria)。

★判断 3(改訂)— A/B の選択は今決めない。Path 非依存の共通核から着手し、顧客の 2 問で分岐する

改訂の理由(独立審査の結果を受けて): 4 案を 2 つのレンズで独立採点させたところ、評価が割れた:

出荷レンズ(一人開発・完遂可能性) 導入レンズ(製造業・稟議)
A 観測される普通の Python 78(1 位) 55(4 位)
B 独自 DSL + VM(当初の本書の選択) 52 61
C ABI-first ネイティブ L1 44 58
D プロセス分離ランタイム 58 74(1 位)

どちらのレンズも B を 1 位にしなかった。 そして両者が独立に同じ結論へ到達した — 決定的な情報は技術側になく、顧客側にある:

Q1「工程技術者が書いた Python を産線の検査 PC で実行することを、顧客の IT / セキュリティ規程は許すか」 (3 社中 2 社以上が拒否なら A は成立しない)

Q2「稼働開始後にレシピを書き換えるのは ベンダのみ(封印)か、顧客の工程技術者か。 そして『以前と同じ判定になる』ことを証明する責任と手段(golden セットの採取・保管・再生)はどちらが持つか」 (顧客が触る → 可読レシピ + 顧客側 golden が必須 = A 型 / ベンダ封印 → 自己申告マニフェストでは証明能力を欠く = B 型)

★決定的な発見: 最初の約 6 週間の作業は A / B / D のどれを選んでも同一である。 両審査員の「接ぎ木すべき要素」がほぼ一致しており、そのすべてが Path 非依存だった:

共通核(今すぐ着手してよい) 由来
fullseye_abi.h を「C コードを 1 行も書かずに仕様として先に書き」、fslib.py をその適合実装にする C 案。審査員が「全 4 案を通じて最良の単一アイデア」と評価。「C ABI に落とせる形だけを許す」が規律でなく機械検査になる
5 件の意味論欠陥を、レシピが 1 本でも外に出る前に閉じる + golden vector 形式の確定 B 案。撤退不能点の封鎖(判断 5)
fail-closed なロード時マニフェスト検証(op ごとに name / backend id / ABI ver / golden の SHA-256 + build ID、不一致は degrade でなく起動拒否) B 案 → A/D 双方へ。§1.6b の fail-open 欠陥への直接の答え
画素ループを「リント」ではなく「表現不能」にする B 案。ラボの 256² では通り産線の 4 MP で溶ける(実測 2.2 s、観測下 13.0 s)= 受入試験をすり抜ける形の破綻
非 GUI 常駐プロセス + timeBeginPeriod(1) D 案。体裁でなくタイマ要件(実測 max 8.905 → 0.340 ms = 26x)
cycle 中のヒープ確保ゼロを CI ゲートで機械保証(arena / 確保カウンタのアサート) C 案。「規律を守っています」と「構造上できません」は受入審査で意味が違う
C ABI shim(200〜400 行)を「DLL が欲しい」への回答として温存 D 案。ビジョンロジックを C へ codegen しない

確定: 共通核を先に作る。A/B の分岐は Q1・Q2 の答えが出るまで確定させない。 これは判断 5(撤退不能点は公開した意味論)と整合する — 共通核はどの意味論も公開しないからである。 fslib.py(実装済み)はこの共通核の一部であり、A でも B でも D でも無駄にならない

★ユーザーへの確認事項(本書で答えられない唯一のもの): Q1 / Q2 を聞ける顧客・見込み顧客が今いるか。 いなければ「何週間で作れるか」。コストはほぼゼロ、期間は数週間で、6〜24 か月の投資先が一意に決まる。


判断 3b(旧判断 3・分岐後に効く)— 二者択一そのものは誤った設問だった。A の実装 + B の規律

検討した Path C / Path D の独立設計と、その自己評価(設計レビューより。どちらも自案の致命傷を自ら挙げた):

★独自 DSL の真のコストは VM ではなく標準ライブラリ(設計レビューの指摘・受け入れ):

DSL のコストは VM ループではなく標準ライブラリと誤り意味論であり、そこが 3 万行級。 Lua は約 17,000〜32,000 行の C を、PUC-Rio のチームが 30 年維持している。

この指摘は正しく、私は過小評価していた。 FSCRIPT_LANGUAGE.md §2 は 異種混在 Tuple + ブロードキャスト / Vector / STL 風コンテナ(list/map/set/stack/queue/pair) / 型変換 / 文字列関数(regexp_match 等)を要求しており、これがまさに 3 万行の領域である。

∴ 言語のスコープを次のように絞る(確定):

対象 増分 1〜3 後回し(証拠が出てから)
制御フロー(if/for/while/break) 入れる
iconic の型と domain 入れる
Tuple(異種混在・要素ごと演算・添字・長さ・連結) 入れる(HALCON 互換の最小核)
数値・文字列の基本変換(int/real/string/str_to_number) 入れる 書式指定の全面、regexp_*
Vector(入れ子・多次元コンテナ) 入れない 実レシピが要求したら
STL 風コンテナ(map/set/stack/queue/pair) 入れない 同上
例外・ユーザー定義 procedure 入れない 同上

判断基準: 言語の仕事は L1 を呼ぶことであって、汎用プログラミング言語になることではない。 「HALCON にあるから入れる」ではなく 「実際の検査レシピが書けないから入れる」でのみ追加する。 FSCRIPT_LANGUAGE.md §2 の STL コンテナ節は 「将来の候補」へ格下げする(本書で上書き)。 → これで 3 万行リスクの大半を削り、増分 1〜3 を「制御 + 型 + Tuple 最小核」に収める

検討したが採らなかった第 3 の案(正直に記録)= 制限付き Python サブセット: ユーザーが書くのは Python だが、ast で構文木を検査し、許可したノード種別・名前・呼び出しだけを通す方式。 利点 = パーサ/VM を自作しない、既存エディタとデバッガが使える、進化エンジンが生成しやすい。 採らない理由: (1) サンドボックス境界が漏れやすいことが広く知られている(属性アクセス・dunder 経由の脱出)、 (2) 何より Python の意味論がそのまま公開仕様になる — これは Codex が「最も危険」と名指しした撤退不能パターンそのもの (§2 判断 5)、(3) ユーザーが方向性として HDevEngine 風の専用言語を既に決定している。 ただし (1)(2) は「実行契約を小さく閉じる」という同じ目的を別手段で満たそうとしたものなので、 もし独自 DSL の保守負担が想定を超えた場合の代替案として記録しておく。

判断 4 — Studio(設計時)と Runtime(配布)を別プロファイルにする

コールドスタート、フットプリント、決定論要件 — 3 つとも同時にこれで解ける。 実測で裏付け済み: 起動 1663 ms → 160 ms(10.4x)、フットプリント 5 GB → 375 MB (fslib.py が 650 op レジストリを import しない設計にした直接の理由)。 160 ms ならウォッチドッグによる復旧再起動が 0.2 秒で終わる = 実運用に耐える。

判断 5 — 撤退不能点は実装言語ではなく「公開した意味論」。ゆえに今固定すべきは契約だけ

Codex の指摘(確信度高)を実測が裏書きした:

本当の撤退不能点は、顧客が保存したプロジェクトとプラグインが、あなたの実行意味論に依存し始めた時。 製造業では「移行ツールを出した」では済まず、以前と同じ判定になる証明を要求される。

§1.6 の欠陥 5 件は、まさに「Python の意味論がなし崩しに言語仕様になっている」状態。 これを出荷する前に閉じることが、本プロジェクト最大の一手。

判断 6 — 「それでは OpenCV の薄いラッパでは?」への答え

本確定案に対する最も強い反論を自分で立てておく:

Python + OpenCV で十分速いと証明したのなら、Fullseye の産業プロファイルは cv2 の 44 op に過ぎない。 顧客は無料の OpenCV を直接使えばよいのでは?

答え: 売っているのは op ではない。 これは HALCON にもそのまま当てはまる反論であり (HALCON の中身の多くも古典的アルゴリズムである)、それでも HALCON が高価に売れている理由が答えになる。 Codex も同じ結論を出している — 「購入判断に効くのは op 数より、再現性・デバッグ・レシピ・PLC 接続・ログ・復旧」。

Fullseye が売るもの、優先順:

  1. ライブウォッチ IDE — image / region / domain(ROI) / XLD / ObjectSet / handle を、実行を止めて その場で見られること。OpenCV には無い。ここが FSCRIPT_LANGUAGE.md §4 の主戦場である理由。
  2. 配布と保証のパッケージ — 決定論プロファイル、init/cycle 分離、deadline と ERROR/TIMEOUT、 常駐 Runtime(起動 160 ms)、閉じた配布イメージ、SBOM、golden image 回帰による「以前と同じ判定」の証明。 OpenCV を直接使う顧客は、これを自分で作ることになる。
  3. アルゴリズムを設計する AI(進化エンジン + 正直な holdout ゲート)— これは競合に無い。 ただし §1.8 のとおり、差別化が効くのは shape-based matching / XLD サブピクセル / 1D measure の 3 領域であって、 op を 650 個並べることではない。
  4. op の網羅は 4 番目。製品価値の順位を op 数トップに置かないことを、ここで明示的に決める。

★この順位付けを裏付ける調査結果: 現在の HALCON カバレッジは 307 / 2313 演算子 = 13.3%。 残 2006 演算子の未カバー最上位は Tuple(165)/ System(141)/ Graphics(174) = まさに言語・型システム・描画層であり、ネイティブコアでは 1 つも解決しない領域。 → 「HDevelop 級」への距離の大半は画素処理ではなく言語基盤にある。 これは判断 1〜3(型と意味論を先に閉じる)と完全に整合する。

含意(ロードマップへの反映): 増分 3(ウォッチ)と増分 4(Runtime)は「後回しの仕上げ」ではなく 製品価値そのもの。増分 2 で速度が出た時点で満足しない。


3. 要件定義

3.1 いま固定する契約(= 撤退不能点を管理下に置く)

R1. 型モデル(iconic / control を厳密に分離し、sort は「運ぶ」— 推測しない)

R2. 演算子契約 LanguageOperatorSpec(C ABI に落とせる形だけを許す)

R3. 意味論の禁止事項(「黙って誤答しない」の明文化)

R4. 決定論と設備との協調(Runtime プロファイル)

R5. 版管理と再現性

3.2 まだ固定しないもの(意図的に自由を残す)

3.3 非機能要件(製造業。§1.2 の「弾かれる条件」を裏返したもの)

# 要件 受入基準
N1 サイクルタイム 対象構成で 最悪値(p99.9 と連続 N 回の max)を提示できること。平均は受入根拠にしない。★必ず「実デューティサイクルで N 分以上運転した熱定常状態」で測る — 同一コード・同一マシンで冷えた状態 10.8 ms → 連続負荷後 18.3 ms(1.7 倍)max は 4 倍ぶれた実測がある(FSCRIPT_MEASUREMENTS.md §0-a)。冷えたマシンの数字は契約に書けない
N2 連続運転 長時間試験(メモリ増加・ハンドルリーク・カメラ再接続・復旧)の結果を出せること。★25 分 / 52,800 サイクルで一次確認済(FSCRIPT_MEASUREMENTS.md §5.6): p50 ドリフトなし(前半 25.98 / 後半 26.26 ms)・リークなし(+1.6 MB = 30 バイト/サイクル)。数時間〜数日規模と実カメラ接続は未測定
N1b 裾を詰めること(★示唆あり・要追加測定 2026-08-16) かつて同 soak で 最悪 118.7 ms(p50 の 4.6 倍) を観測。★本セッションで裾は外部 CPU コア競合に強く依存すると示唆(FSCRIPT_MEASUREMENTS.md §9.1、各 N=1): クリーンなアイドル機は 54,000 サイクルで最悪 21.0 ms(1.24 倍)= タイト、全コア飽和で ~73 ms へ膨張(cv2 系が膨れ numpy threshold は不変)。★ただし 118.7 ms は一度も再現できておらず(最大 ~73 ms)、正本測定が開発機上の並列エージェント汚染だった可能性を排除できない=「環境要因と確定」とまでは言えない。対策候補=コア確保(24 コア機の 1 例のみ・4-8 コア産線 PC で未検証・逆効果の恐れ)/ cv2 スレッド抑制(弱い)/ 優先度(未適用=未検証)。受入前に必須の測定: 4-8 コア機の裾 / ≥3 反復 + p99.9 / per-cycle hard-fault / バッファ pool 版の飽和下 A/B / 118.7 ms の再現 or 汚染源特定。バルク内訳 = connection(cv2)~12 ms + threshold(numpy)~4.9 ms、~14,000 page fault/サイクル(毎サイクル新規 4MP 確保=pipeline 側の増幅器で、pool 化が裾にも効く可能性は未排除)
N3 オフライン復元 インストーラ・wheel・ネイティブ DLL・ドライバ・ライセンス・モデルを完全保存。★凍結は onedir + MSI(--onefile は起動のたびに %TEMP% へ再展開し +1.8 秒、かつ Qt DLL を埋め込むと LGPL 準拠の説明が困難)。★リリースビルドは使い捨てクリーン venv / CI コンテナで行う(汚れた site-packages から過剰収集すると同じアプリが 5.4 GB になる実測あり)
N4 SBOM / ライセンス 第三者ライセンス一覧・GPL 混入なし・Qt/PySide の条件を明示(要法務確認)。★下記の cv2 実態を織り込むこと
N5 サポート性 現地には安定エラーコード、詳細ログに元例外・op ID・入力 shape・プロジェクト hash。サポート bundle を 1 操作で採取
N6 安全 安全機能は担わない(PLC/安全 PLC 側)。非安全系の検査判定に限定することを設計制約として明記
N7 事業継続 一人ベンダーのリスクに対し、ソースエスクロー / 仕様書とテスト資産の整備で応える(技術で消せないと正直に認める)
N8 脆弱性対応義務(EU 市場) EU Cyber Resilience Act(Regulation (EU) 2024/2847)。2024-12-10 発効、Art.14 の報告義務が 2026-09-11 から適用。自社製品に含まれる脆弱性が実際に悪用されていると知った時点で 24 時間以内の報告義務。→ 「装置をネットから隔離してパッチを当てない、と顧客と合意する」という緩和策は EU 市場向けには使えない。SBOM と依存の追跡が契約ではなく法令上の要件になる。要法務確認(本書は法的助言ではない)
N9 CI と C ビルド系が存在しない 自分で一次確認: .github なし(git 管理下に 0 ファイル)、CMakeLists.txt / Makefile / setup.py なし = CI も C 拡張のビルド系も無い。ただし pyproject.toml(setuptools)は在り、wheel ビルド自体は実績がある(STATUS の隔離 venv 検証)。N3(オフライン復元)と N4(SBOM)、および増分 5 のネイティブ化には CI と C ビルド系の新設が前提になる

★ N4 の実態(一次情報で確認、2026-08-15) — 「cv2 = Apache-2.0 だから安全」は誤り:

構成要素 ライセンス 含意
OpenCV 本体 Apache 2.0 問題なし
opencv-python のパッケージングスクリプト MIT 問題なし
全 wheel が同梱する FFmpeg LGPLv2.1 再リンク可能性の提供義務が残る
非 headless の Linux wheel が同梱する Qt5 LGPLv3 Runtime では回避すべき

要件: Runtime は opencv-python-headless を使う(Qt5 LGPLv3 を排除)。 それでも FFmpeg の LGPL は残るので、(i) 動的リンクのまま再リンク手段を提供するか、 (ii) FFmpeg 無しで OpenCV を自前ビルドして完全に切るか、を増分 4 までに決める。 Fullseye は動画 I/O を Runtime で必要としない見込みなので (ii) が本命


4. 基本設計

4.1 二プロファイル

┌───────────────────────────────┐      ┌───────────────────────────────┐
│ Fullseye Studio (設計時)       │      │ Fullseye Runtime (配布)        │
│ ──────────────────            │      │ ──────────────────            │
│ L3 ウォッチ IDE (PySide6)      │      │ GUI なし・常駐・headless        │
│ L2 Fullseye Script VM   ←── 同一 VM ──→ L2 Fullseye Script VM          │
│ L1 native 優先 / numpy 補完     │      │ L1 native のみ (fail-closed)   │
│ + 650 op 進化エンジン + holdout │      │ + init/cycle 分離 + deadline   │
│ + torch / kornia (研究用)       │      │ + PLC state machine            │
│ 起動 1663 ms / 約 5 GB          │      │ 起動 160 ms / 約 375 MB         │
│                               │      │ Python を顧客に公開しない        │
└───────────────────────────────┘      └───────────────────────────────┘
              │                                       ▲
              └── 同じ .fsh スクリプト + 同じ IR version ┘
                  差分は「native が無い op を許すか」だけ

★重要: Studio 側も native を優先する(numpy は native が無い op の補完のみ)。 「設計者が見たものがそのまま出荷される」ため(§4.2)。同じスクリプトが両方で走り、 結果が一致することを差分テストで保証する(§4.5)。

4.2 3 層 + バックエンド選択(R2 の中核)

L3 Studio IDE      … エディタ / step / breakpoint / ★ウォッチ(型別レンダラ登録制)
L2 Fullseye Script … lexer→parser→typed AST→(将来 bytecode)→VM
                     ★ロジックを持たない。L1 を LanguageOperatorSpec 経由で呼ぶだけ
L1 Fullseye Lib    … 1 op = N backend。profile("studio"|"industrial"|"reference") で選択
                     numpy 実装 = 常に存在(= 差分テストの正解オラクル)
                     native 実装 = 任意(cv2 / C / Rust)。差分テストで等価性を証明したものだけ有効

profile の意味(3 つ。★ここは設計上の要点なので厳密に):

profile バックエンド優先順 用途
studio native → numpy 設計者が使う。ラインで走るのと同じ実装を見せる
industrial native のみ(fail-closed) 配布。native が無い op は FsBackendError。静かに遅くならない
reference numpy のみ 差分テストのオラクル専用。設計者向けの既定ではない

★「設計者が見たものが、そのまま出荷される」を最優先の制約とする。 studio を numpy(オラクル)にすると、設計者は numpy の挙動で閾値を調整し、ラインでは native の挙動が走る。 差分テストが差を有界化しても、判定境界ぎりぎりのレシピは反転しうる。 製造業は「移行ツールを出した」では済まず 「以前と同じ判定になる証明」を要求する(§2 判断 5)ので、 設計時と実行時で実装を分けてはいけない。 ∴ numpy 実装の役割はテスト時のオラクルであって、設計者の既定ではない。 (PoC ではこれをテストで固定している: studioindustrial が同一バイト列を返すこと。)

industrial が fail-closed であることの意味: native 実装が無い op を使ったスクリプトは Runtime へ配布しようとした時点でエラーになる。「配布したら遅かった」を構造的に防ぐ。

4.3 実行モデル — VM 一本(判断 2)

4.4 ウォッチモデル(§4 of FSCRIPT_LANGUAGE.md を継承)

型別レンダラ登録制(register_watch_renderer)。control / image(+domain) / region / xld / domain(ROI) / objectset / handle / vector。A/B どちらの Path でも再利用できる核という当初の判断は正しく、維持する。

4.5 ネイティブ化の段階と、その検証(★既存資産の再利用)

TRIZ 原理 #26(コピー): Python 実装を「実行系」から「正解オラクル」へ格下げする。

numpy 実装 (常に存在)  ──┐
                        ├─→ difftest.py: holdout 入力上で max|diff| < tol を要求
native 実装 (任意)    ──┘   不一致 or 未カバーなら industrial プロファイルに載せない(正直に SKIP)

★正直な現況(§1.5 の自己訂正を反映): difftest.py はこの発想を実装しているが、 C 側は一度も走ったことがなく(記録 6/6 skipped)、比較単位も op ではなく champion パイプライン全体。 → per-op 差分ゲートは新規に作る。ただし tests/test_fslib.py で実際に動くことは実証済みなので、 設計リスクではなく実装作業である。 C ランタイムのカバレッジは 8/650 op(1.2%)、かつ region/blob 系ゼロ = 実測で 7.8〜21 倍差が出た側が未着手。 → 産業プロファイルの最初の目標は「op を増やす」ではなく「頻出 20〜40 op に native 実装 + 差分テストを付ける」。 (Codex も同結論: 「526 演算を先に全部移植するのでなく、装置案件頻出 20〜40 演算 + 取得 + 幾何 + 計測 + 通信で 最初の産業 Runtime を成立させる方がよい。購入判断に効くのは op 数より再現性・デバッグ・レシピ・PLC 接続・ログ・復旧」)

native の選択順: ① cv2 で足りるものは cv2(実測済・即効)→ ② 足りないもののみ C(既存 imgops.c を C ABI の後ろへ)→ ③ 本当に必要になったら Rust(単一コードで .dll/.so)。 先回りして Rust コアを書き始めない(市場検証前に数年を投じるのが Path C の致命傷)。

4.6 進化エンジン(650 op)と L1(言語の語彙)の関係 — op を 2 系統にしない

この 2 つは重なるが同一ではない。放置すると「op が 2 系統ある」状態になるので、役割を明示的に分ける:

  api.RT(650 op レジストリ) fslib L1(言語の語彙)
引数 正規化ノブ a/b ∈ [0,1] 意味のある実引数(sigma=1.5, 閾値=0.5)
目的 進化の探索空間(固定長ゲノム・holdout ゲート) 人間が読み書きする語彙(型・単位・エラー)
実装 numpy(研究用に多様性を最大化) 1 op = N backend(native 必須の profile あり)
多いほど良い(654 → 増やす) 少ないほど良い(契約が閉じる)

接続の規約(★これが「進化の北極星」を壊さないための要):

  1. 進化は api.RT の上で回る(現状のまま。654 op、正規化ノブ、locked holdout)。北極星は不変。
  2. 進化が発見した champion は 既存の champion_to_macro.py で macro DNA op に凝縮される(実装済みの機構)。
  3. macro op が言語の語彙になるには 2 つの関門を通る: (a) LanguageOperatorSpec を与える(実引数・型・エラーを人間向けに定義)、 (b) industrial プロファイルに載せるなら native backend + 差分テスト合格
  4. つまり「進化が op を増やす」ことと「言語の契約が膨らむ」ことは切り離される。 650 op はいくら増えてもよい。言語の語彙と産業プロファイルは、通した分だけ増える。

★この分離は一時的な回避策ではなく、恒久的な境界である(敵対検証の指摘で明確化): 進化のゲノムは 固定長 float ベクトル → N_SLOTS 個の線形ステージ(ops.decode / Stage(op,a,b,sort)) という平坦表現であり、正直な holdout ゲートもこの表現の上でしか動かない。 実行時制御変数を持つ言語プログラムは、この表現に写像できない。 → ∴ 言語プログラム全体が進化の対象になることは、原理的に無い。 進化できるのは EvolvableBlock(分岐・副作用の無い直列部分集合)だけであり、 それは制約ではなく正しい境界である。外側の言語が取得・分岐・測定・動作を担い、 block の中だけが進化する — この役割分担を今後も動かさない。

これにより、§3.1 R2 の「正規化ノブと実引数を別契約に保つ」が構造として実現されるEvolvableBlock(FSCRIPT_LANGUAGE.md §5)は、言語の中から (1) の探索を呼ぶための窓口として維持する。

4.7 設備との接続


5. 増分計画

フェーズ I — Path 非依存の共通核(A / B / D のどれを選んでも同じ。今すぐ着手可)

増分 内容 完了条件(falsifiable)
0. 種(★完了) fslib.py = 型モデル(FImage/Region/ObjectSet)+ 3 プロファイル + 1 op = N backend + 差分テスト + fail-closed : tests/test_fslib.py 21 passed、4 MP で 14.2x(§1.7)
I-1. ABI 仕様を先に書く(★完了) C コードを 1 行も書かずに fullseye_abi.h を仕様として書き、fslib.py をその適合実装にする : tests/test_abi_conformance.py 19 passed。ヘッダを機械パースして (1) 演算子集合の一致 (2) arity 一致 (3) ABI 表現可能な型のみ (4) dict が境界を越えない (5) Region が storage を公開しない (6) 例外→status code 対応、を検査
I-2. 意味論を閉じる 欠陥 2〜5 を修正(xfail → pass)+ Tuple 最小核 + domain の実効化 + golden vector 形式(入力 seed + 期待出力 + 許容差)の確定 tests/test_fscript.py5 xfail が全て pass。golden 形式で回帰が回る
I-3. 産業プロファイル + per-op 差分ゲート 頻出 20〜40 op へ backend 拡張 / ロード時 fail-closed マニフェスト検証(op ごとに name / backend id / ABI ver / golden の SHA-256 + build ID、不一致は起動拒否) 4 MP 検査が industrial で p99.9 < 20 ms、全 op が差分テスト合格、依存欠落機で起動しないことを実証
I-4. 周期実行の骨格 + ★裾の切り分け 非 GUI 常駐 + timeBeginPeriod(1) + init/cycle 分離 + cycle 中のヒープ確保ゼロを CI ゲートで機械保証 + deadline と ERROR/TIMEOUTあわせて N1b(裾 = p50 の 4.6 倍)の原因を切り分ける 10 ms ループの max が実測で 1 ms 未満。確保カウンタのアサートが CI で落ちる。4 MP soak の max/p50 が 4.6 → 2 倍以下
I-5. 画素ループを表現不能にする 言語/API の設計で、per-pixel 反復を書けなくする(リントでは不十分) ラボの 256² で通り 4 MP で溶けるコードが書けないことをテストで示す

フェーズ I の途中で、顧客への Q1 / Q2(判断 3)を投げる。

フェーズ II — 分岐後(Q1 / Q2 の答えで決まる)

答え 進む先 内容
顧客の工程技術者が触る & Python 実行が許される A 可読レシピ(Python)+ 顧客側 golden セット + ライブウォッチ IDE
ベンダ封印 & 証明責任がベンダ B 独自 DSL + 封印パッケージ + ロード時検証(共通核の I-3 がそのまま効く)
顧客が C#/C++ に組み込む / OEM / 非 x64 +D or +C C ABI shim(200〜400 行)。ビジョンロジックを C へ codegen しない

いずれの分岐でも フェーズ I は 100% 再利用される。これが「今 A/B を決めない」ことの正当性。

フェーズ I の内部でも I-2 と I-3 の順序は入れ替えない。 意味論が間違ったまま速くしても、間違いが速くなるだけ。

工数の正直な扱い — 点推定でなく「ゲート」で管理する

設計レビューは完全版 Path B(標準ライブラリ込み・二モード)を 18〜26 人月と見積もった (稼働率 70% で暦 14〜22 か月)。この規模は一人では危険であり、だからこそ本書は 2 つ削った:

  1. 配布 codegen を落とした(判断 2)= 二実行系の意味論二重保守が消える
  2. 標準ライブラリを削った(判断 3)= Vector / STL コンテナ / 例外 / procedure / regexp が消える — レビュー自身が「コストの本体はここで 3 万行級」と指摘した部分

それでも点推定は出さない。 参照クラス(Lua = 17〜32k 行の C をチームで 30 年)と この repo の実績(ラッパ実装で 5 稼働日 654 op)は作業の種類が違いすぎて外挿できないからである。 代わりに falsifiable なゲートで管理する:

ゲート 判定 外れた場合
G0 Q1 / Q2 を聞ける顧客・見込み顧客に到達する(数週間) 到達できないなら A/B は決められない。フェーズ I だけを進め、分岐は保留し続ける(共通核は無駄にならない)
G1 I-2(意味論を閉じる = xfail 5 件を pass)を 4 週で終える 見積りが楽観的だったと判断し、制限付き Python サブセット案(判断 3b の第 3 案)へ切り替えを検討
G2 I-3(産業プロファイル 20〜40 op + per-op 差分ゲート)を 8 週で終える 産業プロファイルの op 数を 10 に減らして再挑戦。それも外れたら Path C/D の再評価
G3 ウォッチで実レシピを step 実行しながら image/region/domain を観測できる 製品価値の第 1 位(判断 6)が未達 → 設計全体を再検討

ゲートは「遅れたら諦める」ではなく「見積りの前提が壊れたことを早期に検出する」ためのもの。 遅れた事実そのものより、なぜ遅れたか(標準ライブラリが膨らんだ / 型モデルが決まらない / ウォッチが重い) が次の判断材料になる。


6. 撤退不能点と kill criteria

出荷した瞬間に撤退不能になるもの(= 増分 1〜2 で確定させ、以後は版管理下でしか変えない)

  1. Fullseye Script の文法と意味論
  2. FImage / Region / XLD / ObjectSet / Tuple / handle のデータモデルと値域規約
  3. 演算子名・引数順・既定値・エラー動作
  4. プロジェクト保存形式と IR version
  5. ユーザープラグイン API
  6. 数値結果の互換性保証(= golden image 回帰の形式)

kill criteria(この設計を捨てるべき観測)


7. 正直な限界