Skip to content

vfx runtime: nested layoutsubtree capture canvases hang drawElementImage (check/snapshot time out); ref lookups are unscoped descendant queries #4405

Description

@vanceingalls

Measured 2026-09-24 on hyperframes 0.8.71 (origin/main 58c873d) with the AE exporter's retro-wave export, HF_VFX=1 HF_VFX_REF=1.

Symptom

hyperframes check → Runtime ✗ check_runtime_failure: Runtime.callFunctionOn timed out after "Runtime did not become render-ready within 3000ms" / "Shader transitions did not finish pre-rendering"; hyperframes snapshot --at 2.6 hangs >500 s with no output. Zero console lines are collected in the failing runs (the renderer main thread is blocked, not slow).

Bisect (seven serialized check runs on hand-edited copies)

copy noise+backdrop ref wrapper ext map nested layoutsubtree canvases result
unmodified ✓ ✓ ✓ 2 FAIL
noise chains + data-vfx-for wrappers removed ✗ ✓ ✓ 1 FAIL
map param dropped, ref wrapper kept ✓ ✓ ✗ 2 FAIL
ref wrapper unwrapped ✓ ✗ ✗ 1 FAIL
both shapes gone ✗ ✗ ✗ 0 PASS
E: both shapes kept verbatim; the two INNER self-capture triads removed ✓ ✓ ✓ 0 PASS
F: = E with the two inner layoutsubtree canvases put back ✓ ✓ ✓ 2 FAIL

E vs F differ by exactly two elements: a <canvas layoutsubtree class="hf-vfx-src"> nested inside another capture wrapper's subtree (a data-vfx-ref-visible ref wrapper around a host that runs its own displacement-map chain; a data-vfx-for backdrop wrapper whose preceding-sibling prefix contains a self-capture host). Every runtime path involved — findBackdropWrapper, resolveRefSource, data-vfx-ref-visible, CAPTURE_VISIBLE_SOURCE — is live in E and works. Every await in runtime/vfx.ts is bounded (CAPTURE_PAINT_TIMEOUT_MS), so this is Chrome refusing drawElementImage on a subtree containing another layoutsubtree canvas, not a JS promise.

The exporter is being fixed to never emit the nest (withdraw the chain with a gap). Three runtime items remain:

  1. Design gap: a map/matte ref that names a layer which runs its own kernel cannot be expressed — wrapAsVfxRefSource's precondition "it runs no kernel of its own" is unsatisfiable for such layers. A ref param that may name a HOST and bind its .hf-vfx-out texture directly would remove the need for an outer wrapper.
  2. Latent (M2): host→src/out lookups are unscoped descendant queries — runtime/vfx.ts:486 owner.querySelector("canvas.hf-vfx-src") and :263 host.querySelector("canvas.hf-vfx-out") — while :487 uses :scope > .hf-vfx-in and the comment at :475 states the immediate-child rule. On a nested shape this binds entry.src to the OUTER wrapper and entry.out to the INNER out, so captureSource would draw the canvas the entry paints into. Hidden today only because the nest hangs first.
  3. Latent (M3): resolveCaptureSource hardcodes visible:false (:512) while resolveRefSource reads the attribute (:387) — a doubly-reached canvas gets two VfxCaptureSource objects with opposite keepBitmap; registry order decides.

Suggested runtime hardening independent of the exporter fix: reject (with a reportVfxError) any .hf-vfx-src whose subtree contains another .hf-vfx-src, rather than hanging; and scope the two lookups to :scope >.

Repro copies and logs are on the AE-export machine (/tmp/hf71-bisect-{a,b,b2,c,e,f}, /tmp/fable-coord/hf71-bisect-*.log); exporter-side record: hyperframes-ae-exporter spec §Task 3.8 (2026-09-24).

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions