akari-video 0.1.35 → 0.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/src/history-policy.mjs +140 -0
  3. package/src/store-command.mjs +71 -0
  4. package/vendor/.akari-capability-sources.json +3 -0
  5. package/vendor/docs/contract-2026-07-25-project-structure-v0.md +40 -0
  6. package/vendor/docs/contract-2026-08-28-gpu-export-v0.md +74 -38
  7. package/vendor/docs/contract-2026-08-28-osr-export-v0.md +4 -0
  8. package/vendor/docs/contract-2026-08-30-edit-json-v2-object-tree-v0.md +2 -0
  9. package/vendor/docs/contract-2026-09-02-export-verify-declared-vs-measured-v0.md +3 -1
  10. package/vendor/packages/akari-launcher/package.json +1 -1
  11. package/vendor/packages/akari-launcher/src/history-policy.mjs +140 -0
  12. package/vendor/packages/edit-lint/README.md +18 -0
  13. package/vendor/packages/edit-lint/src/edit-lint.mjs +502 -11
  14. package/vendor/packages/edit-store/lib/internal-model.d.ts +9 -0
  15. package/vendor/packages/edit-store/lib/internal-model.js +31 -0
  16. package/vendor/packages/gpu-export/README.ja.md +24 -17
  17. package/vendor/packages/gpu-export/README.md +27 -18
  18. package/vendor/packages/matte-rvm/README.md +5 -0
  19. package/vendor/packages/matte-rvm/package.json +2 -2
  20. package/vendor/packages/overlay-runtime/README.md +7 -3
  21. package/vendor/packages/project-scaffold/src/index.mjs +6 -14
  22. package/vendor/packages/render-cut/README.ja.md +43 -0
  23. package/vendor/packages/render-cut/README.md +44 -0
  24. package/vendor/skills/analyze-footage/bin/person-matte/mask-from-alpha.mjs +15 -1
  25. package/vendor/skills/analyze-footage/bin/person-matte/mask-roundtrip.mjs +15 -1
  26. package/vendor/skills/analyze-footage/bin/person-matte/person-cutout.mjs +20 -4
  27. package/vendor/skills/analyze-footage/bin/person-matte/person-matte.mjs +134 -20
  28. package/vendor/skills/analyze-footage/bin/person-matte/resolve-packages.mjs +58 -0
  29. package/vendor/skills/analyze-footage/bin/test/person-matte-availability.test.mjs +51 -0
  30. package/vendor/skills/analyze-footage/person-matte.md +42 -16
  31. package/vendor/skills/manage-connections/SKILL.md +1 -0
  32. package/vendor/skills/manage-connections/bin/doctor.mjs +30 -6
  33. package/vendor/skills/manage-connections/bin/resolve-connections.mjs +30 -7
  34. package/vendor/skills/manage-connections/bin/resolve-packages.mjs +58 -0
  35. package/vendor/skills/manage-connections/bin/test/resolve-packages.test.mjs +106 -0
  36. package/vendor/skills/overlay-authoring/3d.md +16 -3
  37. package/vendor/skills/overlay-authoring/motion.md +12 -0
  38. package/vendor/skills/setup-chat-approval/bin/doctor.mjs +30 -9
  39. package/vendor/skills/setup-chat-approval/bin/find-chat-id.mjs +28 -5
  40. package/vendor/skills/setup-chat-approval/bin/resolve-packages.mjs +58 -0
  41. package/vendor/skills/setup-chat-approval/setup-token.md +6 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,140 @@
1
+ /**
2
+ * 変更履歴に何を入れるかの単一の宣言。
3
+ *
4
+ * `clean-manifest.mjs` が「ディスク上で消してよいか」を宣言するのに対し、本モジュールは
5
+ * 「変更履歴に入れるか」を宣言する。この 2 つは別の軸である(納品した mp4 はディスクでは
6
+ * 保持だが、履歴には入れない)。
7
+ *
8
+ * ここが単一の出所である理由: 同じ拡張子の知識が
9
+ * - プロジェクトの `.gitignore` 雛形(節目ごとの自動スナップショットが何を積むか)
10
+ * - `isInternalOrBinaryPath()`(「変更を見る」が何を差分表示しないか)
11
+ * の 2 か所に分かれて存在し、後者だけが育っていたため、書き出した動画が履歴に積まれ続けて
12
+ * `.git` が 4.2 GB に達した(issue #48)。以後はどちらも本モジュールから導出する。
13
+ *
14
+ * 置き場が akari-launcher/src なのは、`apps/shell` の akari-project 拡張が既に
15
+ * `akari-video/src/*.mjs` を直接 import しており(依存追加もロックファイル更新も要らない)、
16
+ * npm 配布 tarball では `src/` がそのまま同梱されるため。`packages/project-scaffold` からは
17
+ * 相対パスで参照する(prepack が本ファイルを vendor ミラーにも焼くので、モノレポと配布物の
18
+ * どちらでも同じ相対パスで解決できる)。
19
+ */
20
+
21
+ /**
22
+ * 書き出し・レンダリング・キャッシュが作る映像/音声/画像の拡張子。
23
+ * 原本(`assets/`)はディレクトリ単位で別に除外するので、ここは「作り直せる生成物」だけを見る。
24
+ */
25
+ export const GENERATED_MEDIA_EXTENSIONS = Object.freeze([
26
+ '.mp4',
27
+ '.mov',
28
+ '.m4v',
29
+ '.webm',
30
+ '.mkv',
31
+ '.avi',
32
+ '.png',
33
+ '.jpg',
34
+ '.jpeg',
35
+ '.gif',
36
+ '.webp',
37
+ '.bmp',
38
+ '.wav',
39
+ '.mp3'
40
+ ]);
41
+
42
+ const GENERATED_MEDIA_EXTENSION_SET = new Set(GENERATED_MEDIA_EXTENSIONS);
43
+
44
+ /** アプリが管理する囲みの開始行。利用者が書き足した行は囲みの外に残す。 */
45
+ export const HISTORY_BLOCK_BEGIN = '# >>> AKARI Video: 変更履歴に入れないもの(この囲みの中はアプリが更新します)>>>';
46
+ /** アプリが管理する囲みの終了行。 */
47
+ export const HISTORY_BLOCK_END = '# <<< AKARI Video: ここまで <<<';
48
+
49
+ const HISTORY_BLOCK_BODY = [
50
+ '# 元の映像と音声はディスクに残したまま、変更履歴には入れません。',
51
+ 'assets/**',
52
+ '!assets/.gitkeep',
53
+ '',
54
+ '# 書き出した映像・音声・画像は edit.json と assets/ から作り直せます。',
55
+ '# ディスクには残ります。変更履歴に入れないだけです。',
56
+ ...GENERATED_MEDIA_EXTENSIONS.map(extension => `*${extension}`),
57
+ '',
58
+ '# 書き出しの一時作業領域・キャッシュ・「変更を見る」の一時ファイル。',
59
+ '.akari/render-tmp/**',
60
+ '.akari/cache/**',
61
+ '.akari/diffs/**',
62
+ '!.akari/diffs/.gitkeep',
63
+ '',
64
+ '# 素材の分析結果は作り直すのに手間がかかるので、変更履歴に残します。',
65
+ '!.akari/sidecars/**',
66
+ '',
67
+ '# パソコンが作る一時ファイル。',
68
+ '.DS_Store',
69
+ 'Thumbs.db'
70
+ ];
71
+
72
+ /** アプリが管理する囲みそのもの(前後の空行は含まない)。 */
73
+ export const HISTORY_BLOCK = [HISTORY_BLOCK_BEGIN, ...HISTORY_BLOCK_BODY, HISTORY_BLOCK_END].join('\n');
74
+
75
+ /** 新規プロジェクトへ書く `.gitignore` の全文。 */
76
+ export const PROJECT_GITIGNORE = `${HISTORY_BLOCK}\n`;
77
+
78
+ /**
79
+ * 囲みを導入する前の世代の `.gitignore` 全文。利用者が一切触っていなければ全文を差し替える。
80
+ * 一致しなければ「利用者が書き換えた」と見なし、囲みを末尾へ足すだけにする。
81
+ */
82
+ export const LEGACY_PROJECT_GITIGNORES = Object.freeze([
83
+ [
84
+ '# Source video and audio are intentionally kept outside the project history.',
85
+ 'assets/**',
86
+ '!assets/.gitkeep',
87
+ '',
88
+ '# Temporary files used by the friendly "変更を見る" view.',
89
+ '.akari/diffs/**',
90
+ '!.akari/diffs/.gitkeep',
91
+ '',
92
+ '# Local operating-system files.',
93
+ '.DS_Store',
94
+ 'Thumbs.db',
95
+ ''
96
+ ].join('\n')
97
+ ]);
98
+
99
+ /**
100
+ * 作り直せる映像/音声/画像か。パス区切りは `/` でも `\` でもよい。
101
+ * 拡張子だけを見るので、置き場所の規則(sidecars は履歴に残す等)は呼び出し側が足す。
102
+ */
103
+ export function hasGeneratedMediaExtension(file) {
104
+ const value = String(file ?? '');
105
+ const separator = Math.max(value.lastIndexOf('/'), value.lastIndexOf('\\'));
106
+ const dot = value.lastIndexOf('.');
107
+ if (dot <= separator + 1) {
108
+ return false;
109
+ }
110
+ return GENERATED_MEDIA_EXTENSION_SET.has(value.slice(dot).toLowerCase());
111
+ }
112
+
113
+ /**
114
+ * 既存の `.gitignore` 本文へ現行の囲みを反映した本文を返す。ファイルが無い場合は undefined を渡す。
115
+ *
116
+ * - 囲みがある → 囲みの中だけを差し替える(外に書き足した行はそのまま)
117
+ * - ファイルが無い・空・旧世代の全文と完全一致 → 全文を差し替える
118
+ * - それ以外(利用者が書き換えている) → 末尾へ囲みを足す(既存の行は消さない)
119
+ */
120
+ export function applyHistoryPolicy(currentText) {
121
+ if (currentText === undefined || currentText === null || String(currentText).trim() === '') {
122
+ return { text: PROJECT_GITIGNORE, changed: true, mode: 'created' };
123
+ }
124
+ const text = String(currentText);
125
+ const begin = text.indexOf(HISTORY_BLOCK_BEGIN);
126
+ if (begin !== -1) {
127
+ const endStart = text.indexOf(HISTORY_BLOCK_END, begin);
128
+ if (endStart !== -1) {
129
+ const next = text.slice(0, begin) + HISTORY_BLOCK + text.slice(endStart + HISTORY_BLOCK_END.length);
130
+ return next === text
131
+ ? { text, changed: false, mode: 'unchanged' }
132
+ : { text: next, changed: true, mode: 'updated-block' };
133
+ }
134
+ }
135
+ if (LEGACY_PROJECT_GITIGNORES.includes(text)) {
136
+ return { text: PROJECT_GITIGNORE, changed: true, mode: 'replaced' };
137
+ }
138
+ const separator = text.endsWith('\n') ? '\n' : '\n\n';
139
+ return { text: `${text}${separator}${HISTORY_BLOCK}\n`, changed: true, mode: 'appended' };
140
+ }
@@ -53,6 +53,56 @@ function findFile(dir, name) {
53
53
  return null;
54
54
  }
55
55
 
56
+ const KNOWN_BUNDLE_COMPONENTS = new Map([
57
+ ['multi-device-combo', ['phone-pro-titanium', 'laptop-slim-aluminum', 'app-icon-squircle']]
58
+ ]);
59
+
60
+ async function readJsonResponse(res) {
61
+ try {
62
+ const data = await res.json();
63
+ return data && typeof data === 'object' ? data : null;
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+
69
+ function componentIds(value) {
70
+ if (!Array.isArray(value)) return [];
71
+ return value
72
+ .map((component) => typeof component === 'string'
73
+ ? component
74
+ : component?.id ?? component?.product_id)
75
+ .filter((id) => typeof id === 'string' && id.length > 0);
76
+ }
77
+
78
+ function bundleDetails(data, productId) {
79
+ const candidates = [data, data?.product, data?.status];
80
+ const products = Array.isArray(data?.products) ? data.products : [];
81
+ const matchingProduct = products.find((product) =>
82
+ product?.id === productId || product?.product_id === productId);
83
+ if (matchingProduct) candidates.push(matchingProduct);
84
+
85
+ const bundle = candidates.find((candidate) => candidate?.kind === 'bundle');
86
+ if (!bundle) return null;
87
+ return { components: componentIds(bundle.components ?? data?.components) };
88
+ }
89
+
90
+ async function resolveBundleDetails(fetchImpl, creds, productId, errorData) {
91
+ const knownComponents = KNOWN_BUNDLE_COMPONENTS.get(productId);
92
+ if (knownComponents) return { components: knownComponents };
93
+
94
+ const fromError = bundleDetails(errorData, productId);
95
+ if (fromError) return fromError;
96
+
97
+ try {
98
+ const productsRes = await fetchImpl(`${creds.url}/products`);
99
+ if (!productsRes.ok) return null;
100
+ return bundleDetails(await readJsonResponse(productsRes), productId);
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+
56
106
  export async function runStoreCommand(args, options = {}) {
57
107
  const log = options.log ?? ((line) => console.log(line));
58
108
  const env = options.env ?? process.env;
@@ -149,6 +199,27 @@ export async function runStoreCommand(args, options = {}) {
149
199
  return { exitCode: 1 };
150
200
  }
151
201
  if (!res.ok) {
202
+ const data = await readJsonResponse(res);
203
+ if (res.status === 404) {
204
+ const bundle = await resolveBundleDetails(fetchImpl, creds, productId, data);
205
+ if (bundle) {
206
+ const componentList = bundle.components.length > 0
207
+ ? `: ${bundle.components.join(', ')}`
208
+ : '';
209
+ log(`セット商品は構成商品を個別に download してください${componentList}`);
210
+ return { exitCode: 1 };
211
+ }
212
+ }
213
+ if (res.status === 404 && data?.error === 'unknown_product') {
214
+ log(`${typeof data.message === 'string' ? data.message : '商品が見つかりません'}(${productId})`);
215
+ return { exitCode: 1 };
216
+ }
217
+ if (res.status === 404 && data?.error === 'artifact_missing') {
218
+ log(typeof data.message === 'string'
219
+ ? data.message
220
+ : '配布物が未入稿です。サポートへご連絡ください');
221
+ return { exitCode: 1 };
222
+ }
152
223
  log(`ダウンロードに失敗しました(${res.status})`);
153
224
  return { exitCode: 1 };
154
225
  }
@@ -93,6 +93,7 @@
93
93
  "packages/intake-form/package.json",
94
94
  "packages/intake-form/README.md",
95
95
  "packages/matte-rvm/package.json",
96
+ "packages/matte-rvm/README.md",
96
97
  "packages/media-bin/package.json",
97
98
  "packages/media-bin/README.ja.md",
98
99
  "packages/media-bin/README.md",
@@ -104,6 +105,8 @@
104
105
  "packages/preview-server/package.json",
105
106
  "packages/project-scaffold/package.json",
106
107
  "packages/render-cut/package.json",
108
+ "packages/render-cut/README.ja.md",
109
+ "packages/render-cut/README.md",
107
110
  "packages/schemas/package.json",
108
111
  "packages/template-render/package.json",
109
112
  "packages/template-render/README.ja.md",
@@ -202,3 +202,43 @@ updated: 2026-08-26
202
202
  }
203
203
  }
204
204
  ```
205
+
206
+ ## 10. 追記(2026-09-03)— 変更履歴に何を入れるか(`.gitignore` 雛形)
207
+
208
+ §2-2 と §9 が扱うのは「ディスク上で消してよいか」である。本節が定めるのは**別の軸**、
209
+ 「変更履歴(節目ごとの自動スナップショット)に入れるか」である。納品した `exports/*.mp4` は
210
+ ディスクでは保持だが履歴には入れない、というように 2 つの軸は独立に決まる。
211
+
212
+ 雛形の `.gitignore` が生成物を除外していなかったため、40 秒の動画 1 本のプロジェクトで
213
+ `.git` が 4.2 GB に達した(6 プロジェクト合計で `.git` が全体の 70%・素材は 4%)。
214
+ 原因は「同じ拡張子の知識が 2 か所に分かれ、片方だけが育っていた」ことである。
215
+
216
+ | 変更履歴に入れる | 入れない |
217
+ |---|---|
218
+ | `edit.json` / `captions.json` / `review.json` / `plan.json` | `assets/**`(原本) |
219
+ | `planning/**` / `.akari/events/**` / `motion/**` | 生成された映像・音声・画像(下表の拡張子) |
220
+ | `.akari/reports/**` の JSON・HTML(証跡の実体) | `.akari/render-tmp/**` / `.akari/cache/**` / `.akari/diffs/**` |
221
+ | `.akari/sidecars/**`(映像を含めて全部) | |
222
+
223
+ - 除外する拡張子: `.mp4` `.mov` `.m4v` `.webm` `.mkv` `.avi` `.png` `.jpg` `.jpeg` `.gif`
224
+ `.webp` `.bmp` `.wav` `.mp3`
225
+ - 例外は 2 つだけである。`.akari/sidecars/**` は分析に手間がかかるため映像でも履歴に残す。
226
+ `.akari/reports/**` は拡張子で分かれ、png は外れ JSON・HTML は残る
227
+ - 単一の出所は `packages/akari-launcher/src/history-policy.mjs`。`.gitignore` 雛形
228
+ (`templates/project-default/.gitignore` と `project-scaffold` の `PROJECT_GITIGNORE`)と、
229
+ 「変更を見る」の除外判定(`isInternalOrBinaryPath()`)は**どちらもここから導出する**。
230
+ 片方だけに拡張子を足すことは契約違反である
231
+
232
+ ### 移行
233
+
234
+ アプリはプロジェクトを開いたとき、`.gitignore` を現行の内容へ揃え、新たに対象となった
235
+ 追跡済みファイルを `git rm --cached` で履歴から外し、1 本コミットする。
236
+
237
+ - **ディスク上のファイルは消さない**(利用者の成果物であり、消してよいかは §9 の宣言表が別に判断する)
238
+ - 履歴そのもの(過去のコミットが抱える blob)は書き換えない。これは利用者の明示操作の領域であり、
239
+ 移行だけでは `.git` は縮まず横ばいになる
240
+ - `.gitignore` のうちアプリが管理するのは `# >>> AKARI Video ... >>>` から `# <<< ... <<<` までの
241
+ 囲みだけとする。囲みの外に利用者が書き足した行は消さない
242
+ - 自分がルートである git リポジトリのときだけ動く。親リポジトリの中に置かれたプロジェクトは、
243
+ こちらが管理していないリポジトリの追跡対象を黙って変えないため対象外とする(§3 の
244
+ 「強制的にファイルを移動・削除するスクリプトは作らない」と同じ抑制)
@@ -13,11 +13,14 @@
13
13
  ## 2. 適格性
14
14
 
15
15
  適格性は宣言時に HTML 文字列、字幕 cue、編集宣言から機械判定する。結果は全件 receipt に残す。
16
+ 自由 HTML の判定は、`<!-- ... -->` の HTML コメントを除去した文字列に対して行う。コメント内の
17
+ `data-akari-3d-scene`、タグ、URL、CSS 語彙は適格性へ影響させず、CSS コメントと
18
+ `<script type="application/json">` の内容は従来どおり判定対象の文字列に残す。
16
19
 
17
20
  | 分類 | 適格 | 意味 |
18
21
  |---|---|---|
19
22
  | `same` | はい | 静的 HTML は起動時、対応済み字幕は unit の初回活性時に 1 回だけスプライト化する |
20
- | `three` | はい | JSON の宣言型 3D scene と描画先 canvas を持つ overlay。毎コマ Three.js canvas を更新する |
23
+ | `three` | はい | JSON の宣言型 3D scene と描画先 canvas を持つ overlay。毎コマ Three.js canvas を更新し、登場表現は `three-scene-entrance-curve` または `three-scene-entrance-sampled` で処理する |
21
24
  | `degraded` | いいえ | raster 自体は可能でも live DOM と同じ時間変化を保証できない |
22
25
  | `unsupported` | いいえ | v0 の表現範囲外であり、正しい完成画を生成できない |
23
26
 
@@ -116,6 +119,9 @@ frame-engine canvas は cuts、layers、transition、matte、LUT を評価する
116
119
  字幕・HTML・3D は LUT の外に置く。全 upload は `uploadPath = "direct"` を必須とし、fallback を検出した
117
120
  コマで書き出しを停止する。
118
121
 
122
+ cuts と layers が同時に空のフレームは、出力解像度の黒 1 枚として合成する。その後の静的 HTML、3D、字幕の
123
+ スプライト合成は通常どおりこの黒い frame-engine canvas の上へ重ねる。
124
+
119
125
  3D は engine の時計から得た local seconds を `threeRuntime.render(container, t)` へ直接渡して駆動する。
120
126
  GPU 出口は overlay sheet の `__akariSeek` を使用しない。毎コマの DOM animation 同期、全 container の
121
127
  visibility 更新、video seek 待ちを 3D canvas の texture 更新へ持ち込まないためである。sheet の
@@ -272,17 +278,26 @@ GPU 出口だけに `--enable-features=CanvasDrawElement`、`--disable-gpu-vsync
272
278
  宣言順で連続する項目をまとめ、静的 HTML、3D、DOM ランを元の index 順に合成したあと、字幕を最後に載せる。
273
279
  すべて LUT の外である。
274
280
 
275
- 次の条件は fail-closed のまま `degraded` とし、receipt に overlay id、理由、検出条件を全件残す。
281
+ CSS 3D は次の 3 群に分けて判定する。
276
282
 
277
- - `iframe`、`object`、`embed` の埋め込み context。
278
- - `perspective`、`preserve-3d`、`rotateX/Y/3d`、`matrix3d`、`translateZ/3d` のいずれかを含む
279
- CSS 3D transform。先行実験では `translateZ` 単独・`perspective` 単独は正しく転写できたため、
280
- 将来はこの粒度まで緩和できる余地があるが、v1 では緩和しない。
283
+ - 幾何(`perspective` / `perspective-origin` / `rotateX/Y/3d` / `matrix3d` / 非ゼロの
284
+ `translateZ/3d`)は `dom` 適格とする。2026-09-03 の 8 fixture × 5 時刻の実測では最大外接矩形内 MAD
285
+ 0.5336(2D ノイズ床 0.1929、予算 1.0)だった。次の例外を維持する。
281
286
  **例外(2026-08-31・issue #34)**: Z 成分がリテラル 0 の `translateZ(0)` / `translate3d(x, y, 0)` は
282
- 2D の `translate` と描画結果が同一(実測: 静的スプライトで全コマ YMAX=0)なので検出しない。
283
- Z が 0 以外、引数の個数が違う、Z が `var()` / `calc()` 等でリテラルとして読めない場合は従来どおり `degraded`。
287
+ 2D の `translate` と描画結果が同一(実測: 静的スプライトで全コマ YMAX=0)なので 3D として検出しない。
288
+ Z が 0 以外、引数の個数が違う、Z が `var()` / `calc()` 等でリテラルとして読めない場合は 3D 幾何として扱う。
284
289
  引数の切り出しは括弧の入れ子を数えるので、`translate3d(var(--x), calc(1px + 2px), 0)` のように X / Y が
285
290
  CSS 変数・calc 駆動でも Z のリテラル 0 を読める(オーバーレイ規約は調整値を CSS 変数に出すため自然に現れる形)。
291
+ - `backface-visibility: hidden` を伴う CSS 3D は `css-3d-backface-hidden` として fail-closed を維持する。
292
+ 2026-09-03 の 8 fixture × 5 時刻の実測では外接矩形内 MAD 13.4318、GPU にだけ現れる画素が最大
293
+ 207,679 px であり、裏面除去は転写されなかった。
294
+ - `transform-style: preserve-3d` は `dom` 適格とする。ただし GPU 経路の遮蔽順は DOM 順になる。
295
+ 子孫の描画域が画面上で交差し、手前の要素が DOM 上で先に描かれる矛盾対を検出した場合は警告するが、
296
+ fail-closed にはしない。
297
+
298
+ 次の条件は fail-closed のまま `degraded` とし、receipt に overlay id、理由、検出条件を全件残す。
299
+
300
+ - `iframe`、`object`、`embed` の埋め込み context。
286
301
  - `requestAnimationFrame`、`setTimeout`、`setInterval`、`Date.now`、`performance.now` で自走する時計。
287
302
  - `video`、`audio`、canvas/宣言型 3D 以外の runtime、JSON 以外の script。
288
303
  - 絶対 URL と外部 font/image/background resource。`background(-image)` の `url(` 走査は宣言の区切り
@@ -301,9 +316,18 @@ texture の左上 4×4 が期待 RGB の ±8 に一致するかを毎コマ検
301
316
  環境では JS channel 指定へ切り替え、その mode も記録する。pixel read は
302
317
  `src/verify-readback.js` に隔離した検証経路だけに許可し、製品経路の読み戻しゼロ契約は変えない。
303
318
 
304
- DOM 層の OSR decode 比較は overlay 外接矩形内 MAD 1.0 以下を、animation 開始時刻を含む代表 5 時刻で
305
- 要求する。sentinel は全要求 frame 一致を必須とする。既知の限界は karaoke word texture、CSS 3D、
306
- 自走時計、3D scene の DOM 入場 animation、OSR/legacy に残る `@property` animation の時刻不整合である。
319
+ DOM 層の OSR decode 比較は、animation 開始時刻を含む代表 5 時刻で、(1) overlay 外接矩形内 MAD 1.0 以下、
320
+ または (2) 構造一致(全画面 MAD 0.2 以下、かつ片側にだけ現れる画素の合計が外接矩形面積の 0.5% 以下)の
321
+ いずれかを要求する。0.5% はアンチエイリアスの差が面積でなく周長に比例して増えることに基づく
322
+ (実測: 二段 preserve-3d の構造一致例は片側 193〜273 px = 外接矩形の 0.136〜0.161% で、
323
+ 外接矩形の周長の約 17%。面が丸ごと出現する不合格例は片側 207,679 px で 3 桁離れている)。
324
+ 外接矩形の面積が 1,000 px 未満へ退化した時刻(例: 全面が真横を向いて消える瞬間)は (1) を使わず
325
+ 全画面 MAD 0.2 以下だけで判定する。`gpu.domLayer.preserve3dOrderConflicts` が非空の overlay は
326
+ この比較の対象外とし、**警告が出ていること自体**を合格条件とする(下記の既知の限界を承知で通すため)。
327
+ sentinel は全要求 frame 一致を必須とする。既知の限界は karaoke の word texture、
328
+ 自走時計、3D scene の DOM 入場 animation、OSR/legacy に残る `@property` animation の時刻不整合に加え、
329
+ `preserve-3d` の子孫が交差すると遮蔽順が DOM 順になることである。検出器は Z 順との矛盾を警告し、receipt の
330
+ `gpu.domLayer.preserve3dOrderConflicts` に残す。`backface-visibility: hidden` は転写されないため degraded とする。
307
331
 
308
332
  決定論には長尺時の既知の限界がある。短い書き出し(実測 450 / 678 / 900 コマ)は 2 走の全コマ SHA と
309
333
  MP4 SHA が一致した。一方、大きな文字を持つ DOM overlay を多数含む長い書き出し(実測 5400 コマ)では、
@@ -355,35 +379,19 @@ receipt の `gpu.captions[].mode = "sprite"` と warning に出して書き出
355
379
  (1.2〜1.3 倍)で、#120f 時点の 1.07〜1.14 倍からは改善している。RSS の上限は 531〜914 MB
356
380
  (1 GB 以内)、`--trap-readback` の読み戻しは 0 だった。
357
381
 
358
- ## 10. v3 — 宣言型 3D の登場曲線
359
-
360
- v3 は、宣言型 Three.js scene のルート要素にある 1 回きりの登場 CSS animation を時刻の関数へ解析し、
361
- 3D canvas の sprite draw state として GPU-native に合成する。Three.js 自体は従来どおり engine clock の
362
- local seconds を `threeRuntime.render(container, t)` へ直接渡す。scene 内部の animation、動画 texture、
363
- ready 判定は変更しない。
364
-
365
- `three` 分類・理由 `three-scene-entrance-curve` にできるのは、次の条件をすべて満たす overlay だけである。
382
+ ## 10. v3 — 宣言型 3D の登場表現
366
383
 
367
- - `<script type="application/json" data-akari-3d-scene>` が属性順にかかわらずちょうど 1 個あり、他の
368
- script がない。
369
- - animation を持つのは HTML のルート要素 1 個だけで、selector は
370
- `[data-akari-active] .root, [data-no-timeline] .root` の対である。canvas と fallback は動かさない。
371
- - animation は 1 本、iteration count は 1、direction は normal、delay は 0 以上、fill mode は
372
- `both` または `forwards` である。timing `linear`、`ease`、`ease-in`、`ease-out`、`ease-in-out`、
373
- または妥当な `cubic-bezier(x1,y1,x2,y2)` に限る。
374
- - keyframe は `from` / `to` または 0% / 100% の 2 点だけで、両端に opacity と transform がある。
375
- transform は `translate()` / `translateX()` / `translateY()` / `scale()` / `scaleX()` / `scaleY()`
376
- だけを使う。px 平行移動と単位なし scale に加え、`var(--name, fallback)`、
377
- `calc(var(--name) + Npx)`、`calc(var(--name) * N)` を受理する。
384
+ v3 は、宣言型 Three.js scene HTML 部分にある CSS animation / transition / `@property` を GPU 経路で
385
+ 扱う。Three.js 自体は従来どおり engine clock の local seconds を
386
+ `threeRuntime.render(container, t)` へ直接渡し、scene 内部の animation、動画 texture、ready 判定は
387
+ 変更しない。登場表現を従来の文法へ解析できる場合は理由 `three-scene-entrance-curve`、解析できない場合は
388
+ 計算済みスタイルを実測する理由 `three-scene-entrance-sampled` とする。解析不能だけを理由に fail-closed
389
+ にはしない。CSS animation のない宣言型 3D は理由 `three-scene-canvas-direct` のままである。
378
390
 
379
- transition、`@property`、複数 animation、複数の animated element、中間 keyframe、iteration count
380
- 1 以外、alternate / reverse、負の delay、fill mode の欠落、未知の timingrotate / skew / 3D transform、
381
- filterclip-path、解決不能な値は不可とし、`three-entrance-*` の具体的な理由で fail-closed にする。
382
- animation のない従来の宣言型 3D は理由 `three-scene-canvas-direct` manifest 形を変えない。
383
-
384
- 解析時は overlay の `vars` と `transform.x / y / scale` を CSS 変数へ解決する。未定義変数の既定値は
385
- 平行移動が 0 px、scale が 1 である。manifest の `entrance` は次の additive な形を持ち、from / to は
386
- 変数解決後の絶対値である。
391
+ curve モードは従来どおり、対になった `[data-akari-active] .root, [data-no-timeline] .root` selector、
392
+ 1 本・2 endpoint keyframe、既知の timing、非負 delay、iteration 1normal direction、`both` または
393
+ `forwards` fillopacity 2D translate / scale だけを解析する。overlay の `vars` と
394
+ `transform.x / y / scale` を解決し、manifest の `entrance` に絶対値を置く。
387
395
 
388
396
  ```json
389
397
  {
@@ -411,6 +419,34 @@ value = from + (to - from) * eased
411
419
  0.00043 px、scale 最大 0.000001、opacity 0 だった。検収閾値は translate 0.5 px 以下、opacity 0.005
412
420
  以下、3D 登場区間の GPU / OSR 外接矩形内 MAD 1.0 以下とする。
413
421
 
422
+ sampled モードは overlay sheet が生成した paused WAAPI clone を使い、毎コマ、OSR と同じ合成時刻
423
+ `seconds * 1000` を `currentTime` に設定する。`data-akari-active` を更新した後、overlay container から
424
+ Three canvas までの各要素について計算済み opacity と transform を読み、transform-origin を含む 2D 行列を
425
+ 上から順に累積する。opacity は積を clamp する。サンプリングは engine clock の時刻だけの関数であり、
426
+ 壁時計や rAF の進行へ依存しない。
427
+
428
+ `@property` を使う断片も `degraded` にはせず sampled として扱う。ただし書き出し用 sheet の WAAPI clone
429
+ 変換は登録済みカスタムプロパティの keyframe を引き継がないため、そのプロパティ自体は GPU / OSR の
430
+ どちらでも補間されず初期値のまま描かれる。同じ keyframe に直接宣言した opacity / transform は補間され、
431
+ 両エンジンの結果も一致するためパリティは保たれる。カスタムプロパティ補間は sheet 側の別課題である。
432
+
433
+ 累積行列が軸平行な translate / scale だけなら 3D canvas を従来の texture のまま使い、中心基準の
434
+ sprite draw state へ変換する。回転またはせん断を含む一般 2D affine は、出力寸法の中間 canvas へ
435
+ `setTransform(a,b,c,d,e,f)` で描いてから恒等 draw state で合成する。perspective、実 Z 成分、その他の
436
+ 3D 行列は理由 `three-entrance-3d-matrix` で `degraded` にする。
437
+
438
+ sampled 方式 A の対象は、断片 root から Three canvas までの祖先チェーン(両端を含む)である。
439
+ このチェーン上の任意の要素にある animation / transition は累積行列へ含める。Three canvas の CSS
440
+ ボックスが出力全面と一致しない場合は、軸平行な行列でも中間 canvas 経路を使い、元の位置と寸法を保つ。
441
+ canvas 以外の HTML(fallback や装飾)を DOM 層で別描画して合成順を保つ方式 B は本版では未実装である。
442
+ 祖先チェーン外に animation / transition がある、または保守的な静的走査でチェーン内だけと証明できない
443
+ 場合は `three-html-animated-descendants` で `degraded` にする。filter / clip-path など他の既存 hard
444
+ blocker も従来どおり fail-closed とする。
445
+
446
+ manifest の各 3D sprite は `entranceMode: "curve" | "sampled" | "none"` を持つ。run payload と receipt の
447
+ `gpu.three.overlays[].entrance.mode` は登場表現について `curve` または `sampled` を記録し、
448
+ `gpu.three.sampling` は sampled フレームの `count`、`p50`、`p95` ミリ秒を記録する。
449
+
414
450
  ## 11. v2 の cut 音声中間物(2026-08-29 追記)
415
451
 
416
452
  GPU 経路の映像は `edit.sources` をページ側で直接読み、`cut.mp4` の映像を使用しない。そのため
@@ -38,6 +38,10 @@ tier 3 の場合に `{ from: "gpu", reason: <launcher.reason> }` を記録する
38
38
 
39
39
  CSS animationはpauseし、`currentTime`を合成時刻へ設定する。Three.jsは対象区間のローカル時刻で描画する。動画要素は提示フレームの確定まで待つ。`frameNumber`はmainから明示的に渡し、秒から再計算しない。
40
40
 
41
+ overlay sheet は各シークで活性区間の自由 HTML 容器へ `data-akari-active` を付与し、非活性区間では
42
+ 除去する。`#stage` の `data-no-timeline` は後方互換のため維持し、どちらの発火ゲートを使う断片も
43
+ OSR で同じタイムライン時刻に同期する。
44
+
41
45
  ## 3. スタンプ行
42
46
 
43
47
  最下1行はフレーム番号 `n mod 65536` を次で符号化する。
@@ -148,6 +148,8 @@ updated: 2026-08-30
148
148
  - 分離(§3.1)は不変条件 1 の帰結として**必ず新しい段を生やす**(同じ段に重ねられないため)。戻せば(⌘Z)段は消える
149
149
  - 音声段も同じ 4 条件に従う(既存の重なり禁止と同じ)
150
150
 
151
+ **総尺の後退規則**: 映像本体(visual レーンの `media` / `telop` / `filter`)の最大終端が 0 より大きい間はその値を総尺の正本とし、その終端が 0 のときだけ、visual レーンの `html` / `group` / `captions` / `caption`(入れ子を含む)と audio レーンの narration / SFX の最大終端から総尺を導出する。BGM は総尺に合わせて切られる素材なので後退対象には含めず、後退対象の最大終端も 0 なら総尺は 0 とする。
152
+
151
153
  ### 2-5 追記(2026-08-31・オーナー裁定): 字幕にも特別な z 規則を置かない
152
154
 
153
155
  - **字幕(袋グループ・分離した行・テロップ変換後)も z は段どおり**。「字幕は常に一番上」という特別規則は**廃止**する(描画側の `generatedFrom` による無条件最上段寄せも撤去)
@@ -32,6 +32,9 @@ issue #45 では、長尺の書き出しが既存の ffprobe 検査をすべて
32
32
  | 既存 `verify.*` | plan の尺・fps・映像 / 音声 codec 等 | ffprobe + 全フレーム decode | 不一致は error | `verification.measured`(従来どおり) |
33
33
  | `verify.audio-level` | `plan.commands.audio_mix.hasAudibleAudio`(BGM / SFX / narration / master)または使用素材の `has_audio` | 最終 MP4 の最大 6 区間へ `volumedetect` | 宣言あり + 最大音量 < −80 dB、または測定不能は error。宣言なし + 音声ストリームありは warning。可聴なら info。音声ストリームなしは skipped | `verification.declared.audio_level` |
34
34
  | `verify.motion-static` | 2 点以上の keyframes で crop または transform が変化する cut(先頭から最大 8 cut) | 差が最大の 2 時点を 160×90 gray で抽出し NCC を計算 | NCC ≥ 0.98 は warning、それ未満は info。一様フレームは skipped | `verification.declared.motion[]` |
35
+ | `verify.blank-frames` | `edit.overlays` / `edit.cuts` の活性区間 | 最終 MP4 の全フレームを 1 パスの `signalstats` で測り、YMAX が背景推定値 + 8 以下に 0.3 秒以上張り付く区間を抽出 | 活性 overlay / cut があれば warning、0 件なら info。error にはせず verdict を変えない | `verification.declared.blank_frames[]` |
36
+
37
+ 空フレーム走査は既定 ON(`--no-verify-blank` で停止)とし、背景 YMAX は出力全体の YMAX 観測値の下位 5% の中央値から推定する。`-skip_frame` と縮小は使わず全フレームを測るため、報告する最小連続長は 0.3 秒である。記録は `{start, duration, ymax_max, active_overlays[], active_cuts[], severity}` とし、既存 11 findings の内容と順序、`verification.measured` および receipt payload の閉じたキー集合は変更しない。
35
38
 
36
39
  `verification.declared.audio_level` は次の形を持つ。
37
40
 
@@ -102,4 +105,3 @@ receipt payload の拡張と edit-lint の変更は、上記候補とは別の
102
105
  - 静止した非一様映像は motion warning、一様映像は `uniform` skip、動く映像は info になる
103
106
  - 既存 11 findings の内容と順序、`verification.measured` のキー集合、receipt payload のキー集合を不変に保つ
104
107
  - warning が verdict、exit code、receipt 作成を変えず、CLI と HTML report では黄色の行として観測できる
105
-
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。 [akari-video npm vendor: bin/akari.mjs is reference-only. These CLI entrypoints are not included in the akari-video npm package. Use `akari doctor --json` and run the path reported in `render_cut.path`. Full installations provide it in a monorepo checkout, ~/.akari/app, /Applications/AKARI Video.app/Contents/Resources/packages, or %LOCALAPPDATA%\\Programs\\@akari-videoshell\\resources\\packages.]",
5
5
  "type": "module",
6
6
  "files": [