Repository navigation
feat: settle on the browser's wovenState, SDK-owned rect cover, same-task resize, double-attach warning, a frame every frame (1.38.0) - #147
Merged
Conversation
This was referenced Oct 8, 2026
…ere the browser reports it (1.38.0) Feature-detected on the prototype; absent attribute keeps the hold path unchanged. firstWoven: confirmed:true, reason 'woven' on the first woven read after a stereo frame. rewoven: needs a withheld/pending read after the call, or 3 frames after a box change, so a stale woven from the old rect never settles it. The cap stays as a safety: withheld forever settles woven:false, confirmed:true, reason <withheldReason>. New read-only handle.wovenState / handle.withheldReason; WovenState / WovenWithheldReason types. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…' on ./splat and ./model (1.38.0) Core primitive on every add*(): after the first join, a change of the canvas's CSS size, dpr or page position raises a sibling 2D cover (color + one left-eye snapshot via drawImage at the change) and cuts it when rewoven() settles; a second change while up restarts; remove() and session end tear it down. 'off' in the frozen core, 'auto' on the scene subpaths. rewoven()'s box key gains the page position (document scroll excluded), so a pure move now restarts it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…cene onResize (1.38.0) The PlayCanvas viewer (./splat playcanvas, ./model default) resizes its store and replays the last frame inside the ResizeObserver callback, before the paint, with the cached frustum's x row scaled by the aspect change so the replay keeps proportions; a callback during a draw or a re-entrant one falls back to the one-frame debounce. Core addScene gains an optional onResize(box) run in the same callback for page-drawn windows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…turns the existing handle (1.38.0) addScene/addImage/addVideo and the addSplat/addModel entries: a canvas still registered on the same wall is answered with its first handle and no second layer/renderer (was: close and rebuild the layer). The subpath entries claim the canvas at the call (their renderer exists before the core window) and pass a claim token to their own addScene; remove() releases it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… first good one (1.38.0) PlayCanvas viewer and SceneViewer drew nothing on a bad frame (short view list, missing or degenerate viewport) before any good frame existed; they now draw the mono camera into both halves (flat subject, or a cleared frame with no content). Rules doc: page-drawn addScene windows must draw every onFrame; a transparent woven frame weaving black is browser-pvt #255. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ds as before (1.38.0) Review: returning the existing handle changed frozen-core behavior (a page re-adding to swap pictures would keep the old one). Detection stays (including the subpath entry claim, so a second addSplat/addModel during the first one's load is caught), warned once per canvas; the call then closes and rebuilds the layer / attaches a new renderer and returns its own new handle, exactly as before. Drops the return-existing machinery (win.handle, the refusal's return value). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
dfattal
force-pushed
the
feat/woven-state-and-guards
branch
from
October 8, 2026 21:54
f41798f to
7ca1805
Compare
…ing null/[] early getDisplayInfo() / getRenderingModes() / requestRenderingMode() called before a window's layer has delivered its first frame (right after add*()) got null / [] / "not forwarded": the browser answers only once the layer has a weave session, and null is documented as "no glasses-free display". The core now waits for that layer's first frame and asks then. - Released by the layer's first stereo frame (the firstWoven signal, no hold), or where the browser reports wovenState, the first non-'pending' read. - Capped at the first-woven cap (REWOVEN_MAX_HOLDS x max(firstWovenHoldMs, 1200) from the layer's construction), checked per session frame and backed by a real timer; a layer that never frames resolves with what it answers then. - No session / no live layer / failed layer: settles at once, as before. Removal or session end mid-wait releases the read, which re-checks and rejects as before; a rebuilt layer waits anew. - index.d.ts documents null/empty as "the layer answered", never "not ready yet"; CHANGELOG Unreleased gains the entry and an intro covering the whole Unreleased set. - test/display-reads.test.mjs (8 tests); display-modes and mode-switch walls now deliver a stereo frame before their display calls. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… an early null On a browser that reports wovenState the release was "first read that is not 'pending'", but the layer reads 'withheld' right after creation, before the weave session can answer: getDisplayInfo() asked at 1.41 s answered null at 1.55 s while the layer only read 'woven' at ~3.9 s (Chromium 156 panel build). - wovenState path: released by the first 'woven' read; 'withheld' does not count. - Both paths: a getDisplayInfo() / getRenderingModes() answer that is null / [] after the release and before the cap is not returned; the read waits for the next release signal (next 'woven' read, or next session frame once a stereo frame has been drawn) and is asked again, resolving with the first non-empty answer. Only past the cap (4 x max(fwHoldMs, 1200) from layer construction) is null / [] returned. requestRenderingMode() is forwarded once, not retried. - Once a read on the layer answered for real, calls go straight through. No session / no layer / failed layer / removed window: unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… after a hold A page that moves its canvas and THEN calls rewoven() never shows a box change or a withheld read after the call, so the confirmed path fell to "a hold of steady 'woven' reads": a kiosk panel run measured 46/46 confirmed at median 1206 ms, the same as the timer path. The no-change rule is now: settle on a 'woven' read at least WOVEN_STATE_LAG_FRAMES (3) session frames after the call. The report is a live per-frame level with at most 3 frames of lag, so such a read reflects the rect as it was at the call. Unchanged: settle after a withheld/pending read seen after the call; 3 frames after a box change seen after the call; never on the frame of the call; a stereo frame since the restart is required; the cap. The timer path (no wovenState) is untouched. Expected ~50 ms instead of ~1200 ms. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #146 (base is its branch; retarget to
mainonce it merges). Five items from a kiosk demo that keeps one woven canvas across screens and hit black boxes after rect moves, a stretched last frame on resize, a stage attached twice, and covers released on timers. Browser side: DisplayXR/displayxr-browser-pvt#256 (join signal, draft PR #258), #255 (transparent frame weaves black).A.
firstWoven/rewoven()settle onXRDisplayLayer.wovenStatewhere the browser reports it (core, additive). Feature-detected on the prototype; absent → the timer path, unchanged. Present →firstWovensettles{ woven: true, confirmed: true, reason: 'woven' }on the first "woven" read after a stereo frame, no hold.rewoven()settles on a "woven" read after a "withheld"/"pending" read seen after the call, or three frames after a box change, never on a stale "woven" the frame the change is seen. Hold cap kept (4 holds, never under 4.8 s): a layer stuck "withheld" settleswoven: false, confirmed: true, reason: <withheldReason>. New read-onlyTileHandle.wovenState/withheldReason, typesWovenState,WovenWithheldReason.B.
rectCoveron everyadd*():'auto' | 'off' | { color?, snapshot? }. A 2D sibling cover goes up when the canvas's CSS box changes (size, dpr, now also position: corerewoven()restarts on a pure move, since the browser re-registers a moved rect), with a one-time left-eye snapshot of the last frame, and comes down whenrewoven()settles. Default'off'in the core,'auto'in./splatand./model(preview-tier default change, flagged in the CHANGELOG). Pages with their own cover set'off'.C. Resize and draw in the same task. The PlayCanvas viewer resizes its backing store and replays the last frame inside the ResizeObserver callback, before paint, with the projection corrected for the new aspect. Core
addScenegains optionalonResize({ width, height, dpr })in the same callback for page-drawn windows.D. Double-attach warning (warning only, no behavior change).
add*()on a canvas still registered, including a secondaddSplat/addModelduring the first one's load, warns once per canvas naming the method and the live registration, then proceeds as before.E. A frame every session frame. Both viewers drew nothing on a bad frame before their first good one; they now draw the mono camera into both halves. Rules doc: page-drawn windows must draw every frame; a transparent frame weaving black is browser-pvt #255, listed as a known limitation.
Tests 849 → 887,
pnpm api:checksnapshots match,pnpm check:snippetspasses. Nothing has run on a browser build withwovenStateor on a panel; all against mocks. Panel checks to do: the WebGL snapshot in B reads back (else the cover is solid), the three-frame lag in A matches the browser, the one-frame move window in B is acceptable.🤖 Generated with Claude Code
F. Display reads wait for the layer's first frame (core, additive, no new API; commit b7f463c).
getDisplayInfo(),getRenderingModes()andrequestRenderingMode()called before the layer has framed now wait for the first stereo frame (or the first non-pendingwovenStateread where the browser reports it), capped at thefirstWovencap (4.8 s by default), then ask the layer;null/empty is returned only as a real absence. Calls after the first frame pass straight through; no-session, failed-layer, removed and session-ended cases settle at once as today. Closes the bug behind the display-modes sample (#151) for every page. Tests 887 → 895.Rebased onto main after #146 and #151 merged; changelog entries sit under Unreleased, no version bump in the PR.
Panel pass 1 (Chromium 156 dev build with
wovenState, 9 Oct):confirmed: truepath works (46/46rewoven(),firstWoven2.97 s vs 4.18 s hold-elapsed), Show fast-switching with the branch andrectCover: 'off'matches the shipped SDK, no double. Two defects found and fixed on the branch: display reads released on the first non-pending read ('withheld' right after layer creation) and answered null early (0748845: release only on 'woven', retry on empty until the cap);rewoven()fell to the 1200 ms steady-state hold when the rect changed before the call (9591a7d: settle on a 'woven' read three frames after the call). Tests 895 → 900. Second panel pass pending.