akari-video 0.1.47 → 0.1.48

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.47",
3
+ "version": "0.1.48",
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": {
@@ -322,6 +322,7 @@ CSS 3D は次の 3 群に分けて判定する。
322
322
  fail-closed にはしない。
323
323
 
324
324
  vgpu の理由語彙は `vgpu-scene-canvas-direct`、`vgpu-stateful-unsupported`、`vgpu-invalid-declaration`、`vgpu-condition:<条件をカンマ連結>`([vgpu v0 契約](./contract-2026-09-06-vgpu-layer-v0.md))。
325
+ `vgpu-scene-stateful-direct` は妥当な `mode: "stateful"` 宣言を表し、状態を持つ WebGPU 効果を固定ステップ replay で canvas 直結する。
325
326
 
326
327
  次の条件は fail-closed のまま `degraded` とし、receipt に overlay id、理由、検出条件を全件残す。
327
328
 
@@ -17,6 +17,7 @@ CPU への画素読み戻しはこの経路に追加しない。ブラウザ内
17
17
  ## 2. 宣言スキーマ
18
18
 
19
19
  断片に次の script をちょうど 1 個置く。canvas は任意で、省略時はランタイムが作成する。
20
+ 以下の例と §2〜§3 のパス仕様は `pure` 用で、`stateful` の追加項目とパス仕様は §7 に定める。
20
21
 
21
22
  ```html
22
23
  <canvas></canvas>
@@ -40,7 +41,7 @@ CPU への画素読み戻しはこの経路に追加しない。ブラウザ内
40
41
  | 項目 | 制約・既定値 |
41
42
  |---|---|
42
43
  | `version` | 必須、数値 `0` のみ |
43
- | `mode` | 必須、`pure` のみ。`stateful` は予約語で拒否 |
44
+ | `mode` | 必須、`pure` または `stateful`(§7) |
44
45
  | `alphaMode` | 省略時 `premultiplied`。それ以外は拒否。シェーダー出力の RGB も alpha 乗算済みにする |
45
46
  | `seed` | 任意の有限数。省略時 `0` |
46
47
  | `uniforms` | 任意の plain object。値は有限数、または長さ 2〜4 の有限数配列。省略時 `{}` |
@@ -106,7 +107,7 @@ fn akari_uv(pos: vec4f) -> vec2f { return pos.xy / vec2f(akari.pad.x, akari.pad.
106
107
 
107
108
  previewScale は辺あたり倍率で、未指定 `1`。推奨値は `1` / `0.5` / `0.25` だが、
108
109
  実装は有限数 `0 < s <= 1` を受け付ける。範囲外は警告 1 回で `1` にする。
109
- preview-server の既定は `PREVIEW_VGPU_SCALE = 0.5`。書き出しは options を渡さず常に `1`。
110
+ preview-server の既定は `PREVIEW_VGPU_SCALE = 0.5`。書き出しは previewScale を省略して常に `1`(stateful は §7 の fps を渡す)。
110
111
  canvas の CSS は absolute / inset 0 / width・height 100% / display block に固定する。
111
112
  内部画素だけが減り、位置・大きさ・時刻・ツマミは変わらない。描画寸法は丸め、最小 1 px。
112
113
  container が 0 サイズなら前回寸法を使い、前回もなければ描画しない。
@@ -124,14 +125,15 @@ GPU 合成側は container の解決済み配置変換をスプライトへ渡
124
125
  | 分類 | 理由 |
125
126
  |---|---|
126
127
  | `vgpu` | `vgpu-scene-canvas-direct`: pure 宣言が妥当で、他条件が canvas 由来だけ |
127
- | `degraded` | `vgpu-stateful-unsupported`: stateful |
128
+ | `vgpu` | `vgpu-scene-stateful-direct`: stateful 宣言が妥当で、他条件が canvas 由来だけ |
129
+ | `degraded` | `vgpu-stateful-unsupported`: v1(§7)で解除。語彙は互換のため残すが、実装はもう出さない |
128
130
  | `degraded` | `vgpu-invalid-declaration`: JSON・version・mode・スキーマ不正 |
129
131
  | `degraded` | `vgpu-condition:<条件をカンマ連結>`: 他の動的条件や外部参照を含む |
130
132
 
131
133
  3D 宣言が同居する場合は、優先して
132
134
  `vgpu-condition:three-or-canvas-runtime(data-akari-3d-scene)` とする。
133
135
  その後、他条件、宣言検証の順に判定する。JSON はちょうど 1 宣言だけを受け付ける。
134
- `forceDegraded` は pure の vgpu 分類を変更しない。degraded になった vgpu は既存の規則どおり
136
+ `forceDegraded` は pure / stateful の vgpu 分類を変更しない。degraded になった vgpu は既存の規則どおり
135
137
  `forced-dom:<元理由>` で DOM 強制の対象となり、eligible は false のまま残る。
136
138
 
137
139
  `summary.vgpu` は件数が 1 以上のときだけ追加する。`spriteManifest.vgpu` は常に配列で、
@@ -140,7 +142,7 @@ receipt の `gpu.vgpu` は run に vgpu があるときだけ、次の形で追
140
142
 
141
143
  ```json
142
144
  { "overlays": 2, "adapter": { "vendor": "apple", "architecture": "metal-3" },
143
- "previewScale": null, "deviceLost": false, "probeMs": 12.5 }
145
+ "previewScale": null, "deviceLost": false, "probeMs": 12.5, "stateful": 0, "replaySteps": 0 }
144
146
  ```
145
147
 
146
148
  overlays は有限非負整数、adapter は文字列、previewScale は数値または null、
@@ -161,6 +163,229 @@ CLI の `--engine legacy` は既に廃止され、引数処理で拒否される
161
163
  vgpu 宣言を検出した時点で `vgpu overlays require --engine gpu` を返す。これが元裁定の
162
164
  「engine 解決後の legacy 拒否」と異なる理由は、CLI からその分岐に到達できないためである。
163
165
 
164
- stateful・feedback・固定ステップ replay は未対応。OSR は同じ sheet を通すが v0 のパリティ gate 対象外。
166
+ stateful・固定ステップ replay §7(v1)で実装済み。texture 種別の state §7 の限界を参照。
167
+ OSR は同じ sheet を通すが v0 のパリティ gate 対象外。
165
168
  Windows / Linux / 他 GPU は未検証。同一機での 2 走・全コマ SHA 一致は実 GPU の検収で確認する事項であり、
166
169
  異機種間の浮動小数点の一致を保証しない。SwiftShader を WebGPU の代替として採用しない。
170
+
171
+ ## 7. stateful(v1)
172
+
173
+ ### 7.1. 宣言
174
+
175
+ `mode: "stateful"` は compute パスと ping-pong 状態を持つ効果を宣言する。
176
+ 宣言の `version` は `0` のまま。トップレベルの許可キーは
177
+ `version / mode / alphaMode / seed / maxReplaySteps / uniforms / state / passes` のみとする。
178
+ `pure` の許可キーは §2 のままで、`state` / `maxReplaySteps` を追加してはならない。
179
+
180
+ | 項目 | 制約・既定値 |
181
+ |---|---|
182
+ | `version` | 必須、数値 `0` のみ |
183
+ | `mode` | 本節では必須、`"stateful"` |
184
+ | `alphaMode` | 省略時 `"premultiplied"`。他の値は不可。出力 RGB は alpha 乗算済みにする |
185
+ | `seed` | 有限数。省略時 `0` |
186
+ | `maxReplaySteps` | 必須、1 以上の整数 |
187
+ | `uniforms` | plain object。値は有限数、または長さ 2〜4 の有限数配列。省略時 `{}` |
188
+ | `state` | 必須、1〜8 個の配列。リソースの仕様は次表 |
189
+ | `passes` | 必須、1 個以上の配列。`init` が 0 個以上 → `compute` が 0 個以上 → 末尾に `fragment` がちょうど 1 個 |
190
+
191
+ `state[].id` は文字列で `^[A-Za-z_][A-Za-z0-9_]*$` に一致し、state 内で一意とする。
192
+ `kind` ごとの許可キーと制約は次のとおり。両種別とも宣言時の検証は行うが、
193
+ texture 種別の実行は 7.6 の制限で拒否する。
194
+
195
+ | `state[].kind` | 許可キー | 制約 |
196
+ |---|---|---|
197
+ | `"buffer"` | `id, kind, bytes` | `bytes` は 4 の倍数の正整数、67108864 以下 |
198
+ | `"texture"` | `id, kind, format, size` | `format` は `rgba16float` / `rgba8unorm` / `r32float` / `rg32float` / `rgba32float` のみ。`size` は `[w, h]`、各要素が 4096 以下の正整数 |
199
+
200
+ パスは `id, kind, wgsl, reads, writes, dispatch` だけを許可する。ただし `fragment` は
201
+ `dispatch` キー自体を許可しない。`inputs` / `scale` は stateful のパスでは未知キーになる。
202
+
203
+ | 項目 | 制約・既定値 |
204
+ |---|---|
205
+ | `id` | 必須の文字列、`^[A-Za-z0-9_-]+$` に一致し、passes 内で一意 |
206
+ | `kind` | 必須、`init` / `compute` / `fragment` のいずれか |
207
+ | `wgsl` | 必須、空白だけではない WGSL 文字列 |
208
+ | `reads` / `writes` | 省略時 `[]`。要素は `state[]` に存在する id の文字列で、各リスト内の重複は禁止 |
209
+ | `dispatch` | `init` / `compute` では必須。`[x, y, z]` の 3 要素すべてが 1 以上の整数 |
210
+
211
+ `init` は state を読めない(`reads` は省略または `[]` のみ)。`writes` は必須で 1 個以上とし、
212
+ すべての `init` を `compute` より前に置く。`compute` の `reads` / `writes` はそれぞれ空でもよい。
213
+ `fragment` の `writes` は省略または `[]` のみで、表示パスから state へは書き込めない。
214
+ 同じ state を compute パスの `reads` と `writes` の両方に指定することはできる。
215
+
216
+ 未知キー、不正値、不正 JSON、宣言数の不整合はすべて `readDescriptor` の TypeError、
217
+ 適格性では `degraded` / `vgpu-invalid-declaration` になる。
218
+ ブラウザの `validateVgpuStatefulDescriptor` と適格性側の検証は同じ規則を使い、
219
+ `alphaMode` / `seed` / `uniforms` と各パスの `reads` / `writes` に上記の既定値を補う。
220
+ WGSL のコンパイルや束縛の実行時エラーは、宣言スキーマの検証とは別に vgpu が検出する。
221
+
222
+ buffer state を 1 本持つ宣言例。`init` / `compute` / `fragment` を各 1 本置き、
223
+ ランタイムが追加する prelude は `wgsl` 内に重複宣言しない。
224
+
225
+ ```json
226
+ {
227
+ "version": 0,
228
+ "mode": "stateful",
229
+ "alphaMode": "premultiplied",
230
+ "seed": 1234,
231
+ "maxReplaySteps": 1800,
232
+ "uniforms": { "stir": 1 },
233
+ "state": [{ "id": "signal", "kind": "buffer", "bytes": 4 }],
234
+ "passes": [
235
+ {
236
+ "id": "init",
237
+ "kind": "init",
238
+ "writes": ["signal"],
239
+ "dispatch": [1, 1, 1],
240
+ "wgsl": "@compute @workgroup_size(1) fn main() { signal_out[0] = 0.5 + 0.5 * sin(akari.seed); }"
241
+ },
242
+ {
243
+ "id": "advance",
244
+ "kind": "compute",
245
+ "reads": ["signal"],
246
+ "writes": ["signal"],
247
+ "dispatch": [1, 1, 1],
248
+ "wgsl": "struct Params { stir: f32 }; @group(0) @binding(1) var<uniform> params: Params; @compute @workgroup_size(1) fn main() { signal_out[0] = signal_in[0] * exp(-akari_state.dt) + (0.5 + 0.5 * sin(akari.time)) * params.stir * akari_state.dt; }"
249
+ },
250
+ {
251
+ "id": "display",
252
+ "kind": "fragment",
253
+ "reads": ["signal"],
254
+ "wgsl": "@fragment fn fs_main(@builtin(position) p: vec4f) -> @location(0) vec4f { let uv = akari_uv(p); let alpha = clamp(signal_in[0], 0.0, 1.0); return vec4f(vec3f(uv, 1.0) * alpha, alpha); }"
255
+ }
256
+ ]
257
+ }
258
+ ```
259
+
260
+ ### 7.2. 束縛と解像度
261
+
262
+ ランタイムは全パスの先頭に §3 の 3 行と `AkariState` の宣言を追加する。
263
+
264
+ ```wgsl
265
+ struct AkariUniforms { time: f32, aspect: f32, width: f32, height: f32, seed: f32, pad: vec3f };
266
+ @group(0) @binding(0) var<uniform> akari: AkariUniforms;
267
+ fn akari_uv(pos: vec4f) -> vec2f { return pos.xy / vec2f(akari.pad.x, akari.pad.y); }
268
+ struct AkariState { step: f32, dt: f32, pad: vec2f };
269
+ @group(0) @binding(2) var<uniform> akari_state: AkariState;
270
+ ```
271
+
272
+ 続いて、**そのパスの `reads` / `writes` に現れる state だけ**を group 1 に宣言する。
273
+ `state[]` の添字を `i` とすると読みは binding `2i`、書きは binding `2i+1`。
274
+ 未使用の番号を詰めず、空けたままにする。下表の `<id>` / `<format>` は宣言値に置き換える。
275
+
276
+ | 種別・用途 | group / binding | 変数宣言 |
277
+ |---|---|---|
278
+ | buffer・`reads` | `1 / 2i` | `var<storage, read> <id>_in: array<f32>;` |
279
+ | buffer・`writes` | `1 / 2i+1` | `var<storage, read_write> <id>_out: array<f32>;` |
280
+ | texture・`reads` | `1 / 2i` | `var <id>_in: texture_2d<f32>;` |
281
+ | texture・`writes` | `1 / 2i+1` | `var <id>_out: texture_storage_2d<format, write>;` |
282
+
283
+ 例えば上の `advance` パスには次を追加する。`signal_in` に ping-pong の `read`、
284
+ `signal_out` に別バッファである `write` を束縛する。
285
+
286
+ ```wgsl
287
+ @group(1) @binding(0) var<storage, read> signal_in: array<f32>;
288
+ @group(1) @binding(1) var<storage, read_write> signal_out: array<f32>;
289
+ ```
290
+
291
+ texture 種別の state を `reads` するパスにだけ、最後に次の sampler 宣言を追加する
292
+ (texture state は現行版では instance 生成時に拒否され、この束縛には到達しない)。
293
+
294
+ ```wgsl
295
+ @group(2) @binding(0) var state_sampler: sampler;
296
+ ```
297
+
298
+ `params` は §2 と同じく断片が `@group(0) @binding(1)` に自分で uniform として宣言する。
299
+ ランタイムは params 宣言を検出したパスにだけ、宣言の uniforms と CSS `--vgpu-*` の解決値を set する。
300
+
301
+ `akari.width/height` は container の等倍実寸、`aspect = width / height`、`seed` は宣言値。
302
+ `akari.pad` はパスの種類で次のように分ける。
303
+
304
+ | パス | `akari.pad` |
305
+ |---|---|
306
+ | `fragment`(表示) | `[描画バッファ幅, 描画バッファ高さ, previewScale]` |
307
+ | `init` / `compute` | `[container 実幅, container 実高, 1]` |
308
+
309
+ previewScale が効くのは表示パスの描画バッファだけで、state の bytes / size は宣言値で固定する。
310
+ init / compute の入力束縛も previewScale から独立するので、同じ入力なら半解像度と等倍で
311
+ シミュレーションの状態が一致する。`akari_state.pad` は常に `[0, 0]`。
312
+
313
+ ### 7.3. fps・固定ステップ・reset
314
+
315
+ `render(container, t, { fps })` の `fps` は **stateful では必須**で、有限かつ正の数でなければ
316
+ `TypeError('vgpu fps is required for stateful scenes')` を投げる。`pure` は従来どおり fps 不要。
317
+ 書き出しは `config.fps`(= `edit.output.fps`)、overlay sheet は `edit.output.fps`、
318
+ プレビューはタイムラインの fps を渡す。`t` は overlay 開始からの有限な局所秒数とする。
319
+
320
+ `dt = 1 / fps`、`targetStep = Math.max(0, Math.round(t * fps))` とする。
321
+ container ごとに `currentStep`(初期値 `-1`)を持ち、target が current より大きければ差分だけ前進する。
322
+ target が current より小さい逆戻りシーク、または未初期化時には内部の `reset()` を行ってから前進する。
323
+ 同じ step への再描画は compute を進めず、表示パスだけを描き直す。
324
+
325
+ reset はすべての buffer state の ping-pong の**両半分**に 0 を書き、`init` パスを宣言順に
326
+ 1 回ずつ実行する。init には `akari.time = 0`、`akari_state = { step: 0, dt, pad: [0, 0] }` を渡し、
327
+ 各パスの直後にその `writes` の state を swap して、最後に `currentStep = 0` とする。
328
+ 両半分を 0 にするので、リセット前の swap の偶奇は結果に影響しない。init を省略した場合は 0 埋めが初期状態になる。
329
+
330
+ 1 ステップは `compute` パスを宣言順に全部実行すること。
331
+ step `n` から `n+1` への遷移では `akari.time = n * dt`、
332
+ `akari_state = { step: n, dt, pad: [0, 0] }` を渡す。
333
+ 各パスに reads / writes のバッファを束縛して `dispatch(x, y, z)` し、直後にその writes を宣言順に swap する。
334
+ 全 compute の後に `currentStep = n+1` とする。
335
+
336
+ 最後に `fragment` を 1 回、`akari.time = t`、
337
+ `akari_state = { step: currentStep, dt, pad: [0, 0] }` で描く。
338
+ 最新の reads を束縛して `frame(gpu, f => f.pass(output, effect))` で surface へ出す。
339
+ 壁時計・rAF・pointer・`Math.random` を状態の入力に使わず、動く入力は宣言の uniforms または
340
+ WGSL 内の `akari.time` の関数で表す。同じ宣言・fps・seed・uniforms のもとでは同じ時刻の絵を
341
+ 直接シークと順送りで再現する。L1 の検収では直接シーク対順送り、および逆戻り対 fresh instance の
342
+ 全画素一致を実測する。
343
+
344
+ ### 7.4. replay 上限と失敗
345
+
346
+ reset 後の `currentStep` を基準に、`render()` 1 回で前進するステップ数
347
+ `steps = targetStep - currentStep` が `maxReplaySteps` を超えたら、次の Error を throw する。
348
+ reset 分はこの上限の計算に含めない。
349
+
350
+ ```text
351
+ VGPU-REPLAY-LIMIT: <steps> steps exceeds maxReplaySteps <n>
352
+ ```
353
+
354
+ container は failed になり、`inspect().status` は `error`、任意の `[data-akari-vgpu-fallback]` を表示する。
355
+ プレビューは捕捉して警告し、書き出しは §6 と同じくエラーを伝播して失敗する。
356
+ 上限を超える巨大なシークを黙って近似したり、途中の状態を完成したフレームとして扱ったりしない。
357
+
358
+ ### 7.5. inspect・receipt
359
+
360
+ `inspect(container)` は §4 のキーを維持し、`stateful`(stateful instance なら true、なければ false)、
361
+ `step`(currentStep、未初期化・pure は null)、`replaySteps`(状態遷移の累計、pure は 0)を追加する。
362
+ receipt の `gpu.vgpu` も既存キーの意味を変えず、次の 2 キーだけを追加する。
363
+
364
+ | キー | 意味 |
365
+ |---|---|
366
+ | `stateful` | stateful な overlay の件数 |
367
+ | `replaySteps` | 各 instance が実行した状態遷移の総数 |
368
+
369
+ `replaySteps` は **reset(step 0 の生成)を 1** と数え、その後の前進は 1 ステップにつき 1。
370
+ 同じ step の表示だけでは増やさず、逆戻りによる reset と再前進は累計に加える。
371
+ 通常の順送り書き出しは以降 1 コマ 1 ステップとなるため、開始から終了まで stateful overlay が
372
+ 1 個ある 3.0 s / 30 fps(90 コマ)なら `stateful = 1` / `replaySteps = 90`。
373
+ mount 時と最初のフレームで同じ step 0 を描いても、reset を重複して数えない。
374
+ 両キーは既存の非負数正規化と `Math.floor` を適用し、欠損・不正値は 0 にする。
375
+
376
+ ### 7.6. 限界
377
+
378
+ **`kind: "texture"` の state は vgpu 0.4.0 では動かない。** 宣言としては妥当なので適格性は
379
+ `vgpu` / `vgpu-scene-stateful-direct` だが、1 個でも含めると instance 生成時に
380
+ `VGPU-STATE-TEXTURE-UNSUPPORTED` で fail-loud にする。container は failed となり fallback を表示し、
381
+ 書き出しも失敗する。根拠は vgpu 0.4.0 の次の実装である。
382
+
383
+ - `dist/set-resources.js` の `normalizeResource` は `case "storageTexture"` で必ず throw し、
384
+ `set()` での storage texture 束縛を無条件に拒否する。
385
+ - `dist/target-offscreen.js` で `pingPong()` が作る offscreen target のテクスチャ usage は
386
+ `render_attachment / texture_binding / copy_src` だけで、`storage_binding` を含まない。
387
+
388
+ v1 の実装対象は `kind: "buffer"`。texture 種別は vgpu 側が storage texture 束縛を提供した時点で解禁する。
389
+ 異機種間の浮動小数点の一致は保証せず、決定論の gate は同一機での 2 走一致とする。
390
+ device lost からの復帰、可変ステップ、状態の保存・復元は対象外。
391
+ `maxReplaySteps` を超えるシークは 7.4 のエラーとして必ず失敗する。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akari-video",
3
- "version": "0.1.47",
3
+ "version": "0.1.48",
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": [
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { spawnSync } from "node:child_process";
3
- import { constants as fsConstants, readFileSync, statSync } from "node:fs";
3
+ import { constants as fsConstants, readFileSync, realpathSync, statSync } from "node:fs";
4
4
  import {
5
5
  access,
6
6
  mkdir,
@@ -15,6 +15,7 @@ import { createRequire } from "node:module";
15
15
  import { fileURLToPath } from "node:url";
16
16
 
17
17
  import { renderLintReport } from "./report.mjs";
18
+ import { describeFragmentAssetHint, extractFragmentAssetReferences, extractAbsoluteFragmentAssetReferences } from "../../render-cut/src/fragment-assets.mjs";
18
19
  import { deriveTracks } from "./derive-tracks.mjs";
19
20
  import { segmentDuration } from "./cut-timeline.mjs";
20
21
  import { musicGrid } from "../../audio-library-setup/shared/beat-grid.mjs";
@@ -2439,6 +2440,7 @@ async function validateOverlays(overlays, timeline, findings, paths) {
2439
2440
  );
2440
2441
  if (!isHtmlFile) continue;
2441
2442
 
2443
+ validateOverlayFragmentAssets(html, overlay, paths, findings);
2442
2444
  const fragment = inspectHtmlFragment(html);
2443
2445
  if (fragment.rootCount !== 1 || fragment.hasTopLevelText || fragment.unbalanced) {
2444
2446
  addFinding(findings, {
@@ -2501,6 +2503,41 @@ async function validateOverlays(overlays, timeline, findings, paths) {
2501
2503
  }
2502
2504
  }
2503
2505
 
2506
+ function validateOverlayFragmentAssets(html, overlay, paths, findings) {
2507
+ if (overlay.html.trimStart().startsWith("<")) return;
2508
+ const root = realpathSync(paths.projectRoot);
2509
+ const outside = target => {
2510
+ const local = relative(root, target).replaceAll("\\", "/");
2511
+ return local === ".." || local.startsWith("../") || isAbsolute(local);
2512
+ };
2513
+ const finding = (reference, check, detail) => addFinding(findings, {
2514
+ severity: "error", check: `overlay-fragment-asset-${check}`,
2515
+ message: `overlay:${overlay.id} fragment ${overlay.html} の参照 "${reference.raw}"${check === "missing" ? " " : ": "}${detail}`,
2516
+ path: relativePath(paths.projectRoot, resolve(paths.projectRoot, overlay.html)),
2517
+ });
2518
+ for (const reference of extractAbsoluteFragmentAssetReferences(html, overlay.html)) {
2519
+ finding(reference, "absolute-path", "断片からの相対パスで書く");
2520
+ }
2521
+ for (const reference of extractFragmentAssetReferences(html, overlay.html, overlay.id)) {
2522
+ const target = resolve(root, reference.path);
2523
+ let actual = target;
2524
+ try { actual = realpathSync(target); } catch { /* Missing files are checked below. */ }
2525
+ if (outside(target) || outside(actual)) {
2526
+ finding(reference, "escapes-project", "escapes the project root");
2527
+ continue;
2528
+ }
2529
+ if (isRegularFileSync(target)) continue;
2530
+ const fallback = resolveLibraryFallback({
2531
+ projectRoot: paths.projectRoot, declaredPath: reference.path,
2532
+ references: paths.assetReferences, akariAssetsDir: paths.akariAssetsDir,
2533
+ });
2534
+ if (fallback.path !== null) continue;
2535
+ finding(reference, "missing", "が見つからない。" + describeFragmentAssetHint({
2536
+ projectRoot: paths.projectRoot, htmlPath: overlay.html, ...reference,
2537
+ }));
2538
+ }
2539
+ }
2540
+
2504
2541
  // --x/--y/--scale/--rotate はランタイム予約変数(renderOverlayNode が
2505
2542
  // .akari-overlay-container へ必ずインライン設定する。packages/render-cut/src/rasterize.mjs)。
2506
2543
  // 断片が var(--x, 80px) のように参照すると、フォールバックではなくランタイムが設定した
@@ -6442,4 +6479,3 @@ function validateAdjustV1Sections(value, findings, path) {
6442
6479
  }
6443
6480
  }
6444
6481
  }
6445
-
@@ -237,4 +237,4 @@ frame-engine bundle は生成物です。`generated/frame-engine.js` を直接
237
237
  有効化できません。DOM frame 検証は隔離した texture sentinel を使い、選択した settle policy
238
238
  (`raf2-paint-event` または `sync-layout`)を receipt に記録します。
239
239
 
240
- 宣言型 `data-akari-vgpu-scene` は同梱 vgpu 0.4.0 の WebGPU ランタイムで描き、描画直後の canvas を GPU 合成へ渡す。v0 は pure な fragment パス、先行パスの texture 入力、CSS `--vgpu-*` ツマミに対応する。ライブプレビューは辺あたり半解像度、GPU 書き出しは等倍で、配置と時刻を共有する。WebGPU の probe・device 障害は `auto` でも失敗として伝播し OSR へ落とさない。receipt の `gpu.vgpu` は使用時だけ追加する。詳しくは [vgpu v0 契約](../../docs/contract-2026-09-06-vgpu-layer-v0.md)。
240
+ 宣言型 `data-akari-vgpu-scene` は同梱 vgpu 0.4.0 の WebGPU ランタイムで描き、描画直後の canvas を GPU 合成へ渡す。v0 は pure な fragment パス、先行パスの texture 入力、CSS `--vgpu-*` ツマミに対応する。ライブプレビューは辺あたり半解像度、GPU 書き出しは等倍で、配置と時刻を共有する。WebGPU の probe・device 障害は `auto` でも失敗として伝播し OSR へ落とさない。receipt の `gpu.vgpu` は使用時だけ追加する。`mode: "stateful"` の宣言(compute パスと ping-pong 状態バッファ)は固定ステップ・seed・リセット再生で決定論・シーク可能になり、receipt の `gpu.vgpu` に `stateful` / `replaySteps` が記録される。詳しくは [vgpu v0 契約](../../docs/contract-2026-09-06-vgpu-layer-v0.md)。
@@ -259,4 +259,4 @@ Frame hashing is available only through the isolated verification module and can
259
259
  with the runtime readback trap. DOM frame verification uses an isolated texture sentinel and
260
260
  records the selected settle policy (`raf2-paint-event` or `sync-layout`) in the receipt.
261
261
 
262
- Declarative `data-akari-vgpu-scene` overlays use the vendored vgpu 0.4.0 WebGPU runtime and transfer each freshly drawn canvas into the GPU compositor. v0 supports pure fragment passes, earlier-pass texture inputs, and CSS `--vgpu-*` uniforms. Live preview defaults to half resolution per edge; GPU export uses full resolution with the same placement and time. WebGPU probe/device failures are fatal, including in `auto` (no OSR fallback); `gpu.vgpu` is present in receipts only when used. See the [vgpu v0 contract](../../docs/contract-2026-09-06-vgpu-layer-v0.md) (Japanese).
262
+ Declarative `data-akari-vgpu-scene` overlays use the vendored vgpu 0.4.0 WebGPU runtime and transfer each freshly drawn canvas into the GPU compositor. v0 supports pure fragment passes, earlier-pass texture inputs, and CSS `--vgpu-*` uniforms. Live preview defaults to half resolution per edge; GPU export uses full resolution with the same placement and time. WebGPU probe/device failures are fatal, including in `auto` (no OSR fallback); `gpu.vgpu` is present in receipts only when used. Declarations with `mode: "stateful"` (compute passes and ping-pong state buffers) are deterministic and seekable through fixed steps, a seed, and reset-and-replay, with `stateful` and `replaySteps` recorded in the receipt's `gpu.vgpu`. See the [vgpu v0 contract](../../docs/contract-2026-09-06-vgpu-layer-v0.md) (Japanese).
@@ -72,6 +72,9 @@ vgpu overlay を扱うホストは、three 群がある場合はその後に
72
72
  プレビューは `render(container, localTimeSeconds, { previewScale: 0.5 })`、非表示化と unmount で
73
73
  `dispose(container)` を呼ぶ。probe と render の失敗を捕捉して任意の `[data-akari-vgpu-fallback]` を表示し、
74
74
  警告は 1 回にまとめる。共有 device は container の破棄時には落とさない。
75
+ `mode: "stateful"` の断片では `render(container, localTimeSeconds, { fps })` の `fps`(= `edit.output.fps`)が必須で、省略すると TypeError になる。
76
+ stateful のプレビューは上の options にも `fps` を加え、`{ previewScale: 0.5, fps }` を渡す。
77
+ 逆戻りシークは reset + 固定ステップ replay で追従し、`maxReplaySteps` 超過は `VGPU-REPLAY-LIMIT` で失敗する。
75
78
  宣言と純関数・解像度の約束は [vgpu v0 契約](../../docs/contract-2026-09-06-vgpu-layer-v0.md) を参照。
76
79
 
77
80
  ## ホストアダプタ契約(新シェル実装者向け — 本パッケージへの入力)
@@ -531,8 +534,8 @@ npm グローバルインストール禁止の制約内で完結するよう、
531
534
  entry `akari-vgpu-entry.js`:
532
535
 
533
536
  ```js
534
- import { init, effect, surface, target, frame, sampler, uniforms, clock, VGPUError } from "vgpu";
535
- window.AkariVgpu = Object.freeze({ init, effect, surface, target, frame, sampler, uniforms, clock, VGPUError });
537
+ import { init, effect, surface, target, frame, sampler, uniforms, clock, compute, pingPong, pingPongStorage, storage, VGPUError } from "vgpu";
538
+ window.AkariVgpu = Object.freeze({ init, effect, surface, target, frame, sampler, uniforms, clock, compute, pingPong, pingPongStorage, storage, VGPUError });
536
539
  ```
537
540
 
538
541
  再生成(空の一時ディレクトリ、macOS arm64。リポの package.json / lock は変更しない):
@@ -557,7 +560,7 @@ wc -c vgpu-bundle.js
557
560
  shasum -a 256 vgpu-bundle.js
558
561
  ```
559
562
 
560
- 実測: **152953 bytes** / SHA-256 `9251bb41157e72e36211af2d0766de72db7251023fb2b968c15bdc529c111c25`。
563
+ 実測: **156447 bytes** / SHA-256 `adf071ead35c31bdaabaa2c65ea18c8a5a2502b5ed122c61a7ff88048615f8a3`。
561
564
  metafile の npm 入力は `vgpu` / `@vgpu/core` / `@vgpu/wgsl` の 3 パッケージのみ。
562
565
  上記 grep は **0 件**。`vgpu/node` / `vgpu/three` / `vgpu/mock` の entry は使用しない。
563
566
  唯一の URL 文字列 `https://github.com/vercel-labs/vgpu/issues/294` はエラーメッセージの参照先で、実行時 fetch ではない。
@@ -23,6 +23,21 @@ accepted when its resolved target is a regular file inside the real project root
23
23
  resolves outside the project is rejected; use the declared asset-library fallback when an external
24
24
  library asset is intended.
25
25
 
26
+ ### Overlay fragment assets
27
+
28
+ Relative asset references inside an overlay fragment resolve from **the fragment file's directory**.
29
+ For example, `overlays/lower-third/fragment.html` resolves `../../assets/logo.png` to
30
+ `assets/logo.png` (`../assets/logo.png` resolves to `overlays/assets/logo.png`). During export,
31
+ images and fonts are embedded as data URIs; video and audio are served through `/media/`.
32
+ Existing `/media/…` and `data:` references are left unchanged. Inline HTML without a fragment
33
+ file path retains its existing behavior.
34
+ Preview resolves asset URLs from the same fragment directory.
35
+ `edit-lint` reports missing, escaping, and absolute local fragment asset references as errors.
36
+
37
+ Missing assets stop export with the overlay ID, fragment path, and reference in the error.
38
+ Embedded files must be at most 16 MiB; reduce larger assets or use video. Video and audio must
39
+ be inside the project, even when an asset-library fallback exists.
40
+
26
41
  ## Default output name
27
42
 
28
43
  Without `--out`, render-cut writes to `exports/` and chooses the stem in this order: