Skip to content

[fix] Compressed (KTX2) textures load again: basis transcoder built without eval - #480

Closed
AlexZ005 wants to merge 194 commits into
feat/70from
fix/ktx2-noeval
Closed

AlexZ005 wants to merge 194 commits into
feat/70from
fix/ktx2-noeval

Conversation

@AlexZ005

Copy link
Copy Markdown
Collaborator

What

Since 1.43.1 a build's CSP has script-src 'self' 'wasm-unsafe-eval' blob: … with no 'unsafe-eval', and that stays. three.js's prebuilt basis_transcoder.js (embind glue) builds its invokers with newFunc(Function, args), and KTX2Loader's blob: worker inherits the page policy. So every KTX2 texture failed with an EvalError: import twins never formed (full-size textures stayed in GPU memory) and "For Quest / web" exports could not be opened back. ktx2-twins and export-presets were red/hung on production.

Fix

  • static/basis/basis_transcoder.{js,wasm}: the same source (basis_universal v1_50_0, 051ad6d8) with the same flags and toolchain (Emscripten 3.1.64), plus -s DYNAMIC_EXECUTION=0 -s EMBIND_AOT=1. That is basis_universal's own opt-in no-eval build (BinomialLLC/basis_universal@b15211474d, issue Bump the dev group with 5 updates #464). Upstream still ships the eval build, and three.js closed KTX2Loader: bundled basis_transcoder.js requires CSP 'unsafe-eval', but rebuilding with -sDYNAMIC_EXECUTION=0 fixes it mrdoob/three.js#34389 as upstream. The wasm is byte-identical. The JS goes from 57,529 to 66,534 B (gzip 15,060 → 15,501).
  • scripts/build-basis-transcoder.sh reproduces the build exactly. No cmake is needed: it runs the commands CMake generates. With --reference it rebuilds the stock flags and reproduces three's two files byte for byte (sha256 8478b5b6… / 6cf17dc8…), which is how the toolchain was pinned. Rebuild time: ~10 s compile + link; the Emscripten install takes ~200 s once.
  • scripts/no-eval-scan.cjs (acorn) finds eval and the Function constructor used as values. A grep for new Function misses newFunc(Function, …). tests/unit/basisNoEval.test.js runs it over static/**/*.js, so a vendored three update can't bring the eval copy back. It fails on v1.44.0's transcoder and passes now.
  • tests/e2e/csp-build.test.cjs now also transcodes an ETC1S and a UASTC KTX2 under the build's policy.
  • static/basis/README.md records provenance, sizes and sha256s, and how to rebuild after a three update.

Proof

All runs are on a built app (scripts/serve-build.cjs, production's CSP meta) on a pc slot.

fix v1.44.0 transcoder, same CSP production 1.44.0
csp-build 8/0 7/1 (KTX2: no answer in 30 s) —
ktx2-twins 20/0 — 10/4 (EvalError), then timeout
export-presets 19/0 — hangs (timeout)
code-popout 40/0 — 39/1 (fixed 1.5 s premise vs a 1.7-2.7 s first-script boot; not eval)

Transcode speed: 12 KTX2 textures → BC7 through the app's KTX2Loader on an RTX 4090.

  • Parallel load median: 53.1 / 45.4 ms with the fix vs 51.3 / 54.2 ms with the old copy (old copy measured with 'unsafe-eval' added).
  • Per texture: 11.0 / 10.2 vs 10.8 / 10.5 ms.
  • In Node, the transcoded output is byte-identical.

Unit tests 23/0, svelte-check ratchet 287/39 (= baseline).

Not a security advisory: nothing is exploitable and the CSP is unchanged.

🤖 Generated with Claude Code

AlexZ005 and others added 30 commits October 22, 2026 17:57
…tic generator

- terrainScatterCore: a set may carry seed + gen {count|density, scale range, rot, spacing}
  (GENERATED slots, each drawn from its own (seed, k) stream so a slot is a stable id),
  ov {[slot]: {x,z,yaw,scale,del}} (hand edits a re-roll keeps), sources [{source, w}]
  (a mixed set; painted records keep their pick in `pick`), tint, sink; every field optional,
  so a 1.38 set normalizes to the identical object and the block stays v1 until one is used
- instancesOf (the ONE list: generated + overrides, then painted, ids g<k>/p<j>),
  withOverrides, withSeed, generateSlots (place/accept hooks for 51-surface), pack sources
- terrainSurface draws per source with tint colours and sink, colliders from the same list
  (one entry per source of a mixed set), pack items load through packRefs
- terrainPaint: writeScatter / editScatterSet / editScatterInstances / rerollScatterSet /
  addScatterSet / scatterInstancesOf; the brush keeps v2 fields, counts generated instances
  toward its density and erases them as deleted edits
- vitest scatterSetFormat (25): determinism twice + across two wire hops, stable slots,
  masks, spacing on raw slots, re-roll keeps edits, migration byte-identical, weights

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…wn file

- ScatterSection.svelte = the Scatter <Section> block of TerrainPaintSection, moved
  verbatim (set list, align, collider, remove, Scatter... button)
- TerrainPaintSection renders <ScatterSection {object} /> where the block was, so the
  terrain Inspector is unchanged; any mesh can carry the section from here on (E2)
- svelte-check 299/39 = baseline

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e/scale/relax brushes, per-source erase, per-terrain splat detail

- scatterEditCore.js (pure, vitest): box pick by projected point, rotate/scale/delete/re-roll
  a selection, group transform about a pivot, rotate/scale/relax/erase brushes (rate-based,
  smoothstep falloff, relax deterministic and clamped to the brush), yaw from a quaternion
- scatterEditAdapter.js: the ONE file that knows the set format (today the 1.38 instance
  bytes; re-pointed at 51-sources' overrides when it lands, QUESTIONS-51-edit Q1)
- scatterEdit.js: Scatter brush TOOLS Paint/Erase/Rotate/Scale/Relax/Select + Affects
  (one model or all = per-source erase); Select = click (Shift/Ctrl adds), box drag, Ctrl+A,
  Delete, Esc; the shared gizmo on a scene-root proxy (move on the ground, turn about up,
  uniform size) previews live and commits ONE terrainScatter entry per drag; toolbox Turn /
  Bigger / Smaller / Re-roll (keeps every other instance's edits) / Delete
- every op = applyTerrainScatter + the existing objectParameters terrainScatter message +
  one terrainScatter undo entry: peers, undo and save need nothing new
- per-terrain splat resolution: Inspector > Terrain paint > Detail 64..512, the map resampled
  (weights re-normalised to 255, coverage kept), one terrainSplat entry + message
- terrainSurface: InstancedMeshes carry scatterIds (picking), scatterHolderOf(); terrainSculpt
  routes the edit brushes; Scene: proxy drag/change + the Select tool's press/move/release
- four duotone tool glyphs; debug hooks scatterEdit / scatterEditCore

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…and a brush on any mesh

- buildSurface(positions, index?, normals?): the host's LOCAL triangles + an area CDF;
  placeOnSurface(u, v) is uniform by area (u picks the triangle, its remainder + v the
  barycentric), smooth normals interpolated when the mesh has them
- generateSurfaceSlots(gen, S, {draws}): FILL by count (keeps drawing until count are
  PLACED, cap 30 x count attempts) or density (density x area attempts), masks slope /
  height (local frame, so a moved host never reshuffles) / painted terrain layer, minimum
  spacing as 3D Poisson-disk dart throwing in slot order; slot k = the caller's per-slot
  stream, so raising the count adds without moving and every peer gets the same layout
- surfaceDab: the brush on a curved surface (sphere around the hit, the terrain brush's
  density + spacing rules), eraseInSphere, a triangle grid for the query
- 16-byte records for a mesh host's painted instances (xyz over a stored box, octahedral
  normal, yaw, scale) + boxOf / boxFrom
- vitest scatterSurfaceCore: 25 checks incl. two runs + two peers on one seed, spacing with
  its counterfactual, saturation, records round trip; PERF on 99,904 triangles: build
  ~11-25 ms, fill 5,000 ~5-10 ms, fill 5,000 + spacing 0.3 + slope mask ~26-54 ms (node,
  warm vs cold) - no worker needed
- svelte-check = baseline, vitest 2515 green

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…look, mixed weights, tint, sink

- scatterSources.js: ONE catalog (primitives, Library, pack items, prefabs) shared by the brush
  toolbar, Properties > Scatter and the object menu; a prefab or a scene object scatters as its
  LOOK only (userData dropped, base at y 0, GLB filed in the Library by hash + pushed to peers)
  with a note the source row shows; a pristine kit piece scatters as its pack item (no bytes)
- object menu: "Use as scatter source" (sets the brush's source)
- Properties > Scatter: per-set model list (weights for a mix, remove), "Add a model to the
  mix", Tint and Sink scrubs (one undo entry + one message per drag), Re-roll for seeded sets,
  counts read what a set DRAWS
- terrainScatterCore: withSourceAdded / withSourceRemoved (a removed model's painted records go
  with it; a mix of one becomes a plain set); terrainPaint: addScatterSource,
  removeScatterSource, setScatterSourceWeight, scatterBlockNow; debug hook scatterSources

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… (checkJs covers tests/unit)

- svelte-check back to 299/39 = baseline (phase 1 had added 50, all in the test fixtures)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…acing in the set format

E2 - scatter on ANY mesh
- Inspector: the Scatter section (ScatterSection.svelte) renders on every plain mesh right after
  the Water section (Add bubble / pour emitter); its "Add scatter" (#scatter-add) adds a seeded
  FILL set with the brush's current source; a Terrain keeps its section beside Terrain paint
- the set format (terrainScatterCore, 51-sources' file, minimal): a set on a non-terrain mesh
  carries host: 'mesh' + box; its painted records are 16 bytes (scatterSurfaceCore); absent on
  every terrain set, so 1.38 terrain blocks stay byte-identical; overrides may carry y
- terrainSurface: ONE instance list per set (setInstances / scatterSetInstances) for the
  renderer, colliders, brush and edit tools; a mesh host fills its own triangles (surface
  cached per geometry identity); instances live in the host's LOCAL frame, so the existing
  holder (matrixWorld copy) makes them follow a move / turn / scale on every peer
- the brush on a mesh: enterSculpt(uuid, {scatter: true}) = a scatter-only session that never
  touches the geometry (no soup conversion, no size cap, nothing sent); the sphere brush
  (surfaceDab) paints ON the surface, erase removes inside the sphere; one undo per stroke
- SculptToolbar offers Scatter on meshes (Paint stays terrain-only)

E3 - fill mode and masks (ScatterFillRows.svelte, per set)
- Count | Density (Segmented), minimum spacing, slope range, height range (local frame),
  painted terrain layer + coverage, Re-roll, Remove fill; drags preview throttled and commit
  ONE undo entry on release
- COUNT means count PLACED (generateSlots keeps drawing until placed, cap 30 x count) on
  terrains too; terrain masks read the height grid + the splat (a layer-masked fill re-lays
  when the paint changes); the Inspector shows the DRAWN count per set (scatterCounts)
- vitest: scatterSurfaceCore 28 (+ mesh-set format, ids on two peers, edits through the
  16-byte codec); scatterSetFormat's mask test updated to count-placed; 2541 green
- svelte-check 299/39 = baseline; e2e suite scatter-surface written, queued (snapshot run)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…atter host too

- terrainSurface.groupSurfaceOf: every child mesh merged into ONE surface in the group's own
  frame (relative transforms + normal matrices baked), keyed by each child's geometry and pose so
  a refilled kit piece or an edited child re-lays; terrainBounds falls back to it for a group
- isMeshHost: a mesh, or a non-light group with meshes under it; the Inspector's Scatter section
  and the scatter-only brush session take it
- Scene: the sculpt/scatter raycast is recursive for a group host, the brush ring takes the hit
  child's normal
- e2e scatter-surface gains a group-host section (two boxes grouped, a slope-masked fill on both
  top faces, identical on B)
- svelte-check 299/39 = baseline

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tools

- setScatterSelection records the scatter block it was taken against, so the onRevision
  check no longer reads our own (or an undone) block as a foreign change and drops a
  real click's selection or the re-selection after an undo
- e2e scatter-edit: rotate / scale / relax brushes, the Affects-filtered erase, one undo
  entry per stroke, the second peer equal, per-terrain splat detail
- e2e scatter-select: a REAL click and a REAL box drag, the gizmo move / turn / size (one
  entry per drag), Turn / Bigger / Re-roll (keeps every other instance) / Delete, Delete
  and Escape owned by the selection, the toolbox holding still; toolbox shots in
  dark / light / contrast. Split in two for the runner's 480 s cap

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…atter set

- scatterConvert.instancesToObjects: the selection or a whole set becomes real objects at
  each instance's exact pose (ground, turn, slope tilt, size); one template per source
  (primitive / Library model / prefab - a logic prefab comes with its logic), duplicated
  per instance; asks first above 500; ONE history batch, so one undo turns them back
- objectsToScatter: several copies of ONE object (a primitive or one Library model) on a
  terrain become instances of that source (position, turn, size kept) and the objects go,
  in one batch
- entry points: Select tool "To objects", Properties > Scatter row "To objects", object
  menu "Make scatter" for a multi-selection
- terrainSurface.scatterPrimitiveSpec (the create command, offset, colour, roughness a
  primitive instance is drawn with); debug hook scatterConvert
- e2e scatter-convert: both directions, the 600-instance confirm, peers, undo, save/reload

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…evice line

- scatterThinCore.js (pure, vitest): thinPlan walks instances nearest first and
  stops where the triangle budget runs out; NEAR_KEEP 30 m is never thinned; the
  last 20 % to the cutoff shrinks smoothly (smoothstep), so the edge reads as a
  fade; scatterBudget = half the target's line; thinProfileOf picks quest / phone
  / desktop from the XR session and a coarse pointer.
- scatterThin.js (runtime): every scatter InstancedMesh registers its full
  matrices, colours and ids; one plan per frame at most every 150 ms or after a
  0.75 m move; kept instances are compacted to the front and `count` lowered;
  userData.instanceIds is rewritten in DRAW order so a pick maps instanceId right.
  LOCAL only: nothing is saved or sent, and colliders never read it
  (scatterPhysicsSets builds every instance from the set).
- Settings > Scene > Performance "Distant scatter" (Auto / Headset / Phone /
  Desktop / All), safeStorage key scatterDetail.
- budgetMeterCore: a thinned InstancedMesh counts userData.instanceTotal, so the
  meter shows the content, not what this device happens to draw.
- e2e scatter-sources: desktop + N6 folded peers; params + seed in one message
  (317 bytes for 6000 instances); the N6 draws 1488 of 6000 inside its 250k line
  with every instance within 30 m drawn; a ball dropped on a rock the N6 does not
  draw rests on it there and at the same height on the desktop; hand edits +
  re-roll; a weighted mix through the real select; tint + sink; a prefab as a
  look-only Library source; a 1.38 block stored byte-identical.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-independent order

- groupSurfaceOf sorts the child meshes by their pose in the group, vertex count and name (never
  a uuid, never tree order: a peer that built the group from messages adds the children as they
  arrive), so a seeded fill lands on the same triangles everywhere
- e2e scatter-surface: density checked at the format's 4/m2 cap, spacing at a packable 0.2 m plus
  a past-saturation check (COUNT places what fits and stops), the grouped boxes are placed with
  replicated moves - 39/39 green two peers; terrain-paint 32, terrain-sculpt (2x lane, 2x base
  A/B: an earlier one-off red was a flake), mesh-sculpt 13, scatter-colliders 23 green

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…moved Scatter section

- 51-sources added its per-set rows (model mix + weights, add a model, tint, sink) to the
  Scatter section in TerrainPaintSection.svelte; 51-surface had moved that section VERBATIM
  into ScatterSection.svelte (512623e, QUESTIONS Q2). TerrainPaintSection keeps our side;
  their rows, scrub helpers and imports now live in ScatterSection.svelte.
- Drawn counts stay on scatterCounts (masks + mesh surfaces aware); their "Generated from
  seed N" Re-roll row is folded into the Fill block's Re-roll (the seed is in its title),
  so a set shows one Re-roll.
- withSourceRemoved honours a host:'mesh' set's 16-byte records through io.records (as
  withOverrides does); rewriteSet passes surfaceRecords.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…o the 51-sources format

Conflicts (QUESTIONS-51-surface A2), both sides kept:
- SculptToolbar: Scatter panel on any host; source note row kept; 51-edit's tool row
  (Paint/Erase/Rotate/Scale/Relax/Select) on a terrain, Paint + Erase (the 47 erase flag)
  on a mesh host; Affects lists the drawn count + sourceLabel.
- TerrainPaintSection: <ScatterSection> kept, 51-edit's Detail (splat resolution) row kept;
  the per-set "To objects" button moved into ScatterSection (terrain sets).
- terrainSculpt: exitSculpt clears sculptScatterOnly AND ends the edit session;
  paintOp keeps scatter on meshes, 'scatter-edit' on a terrain only; strokeMove passes y;
  startScatterEditSession moved back into enterSculpt (auto-merge had put it in
  enterMeshScatter, where isTerrain does not exist).
- terrainSurface layoutScatter: HEAD (sources/tint/sink/thinning); picking reads
  mesh.userData.instanceIds.

Format seam (QUESTIONS-51-edit Q1): scatterEditAdapter re-pointed at 51-sources' API -
lists = scatterSetInstances (string ids g<k>/p<j>), blockOf diffs each edited list into
withOverrides (moves -> overrides, drops -> del, painted records rewritten; seed/gen/
sources/tint/sink ride through), id-less entries append painted records. Picking maps
instanceIds -> list index; scatterEditCore setPoses/transformGroup keep id/src/tint;
scatterConvert makes each instance of a mixed set from its own source and honours sink.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…verrides, painted rewrites, appends)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
three itself stays 0.186.1 (already npm latest; Dependabot moved it in 19ddcb6, shipped
since 1.18.0). r186 migration notes audited against src/ and scripts/: no use of
toTrianglesDrawMode, LightProbeGrid*, Source, SimplifyModifier, PCFSoftShadowMap on
WebGPU, GTAONode, the Sky `up` uniform, and no Object3D subclass with its own dispose().

- three-mesh-bvh 0.9.15 -> 0.9.16: BVHHelper fix for r186 (peer three >=0.159).
- @threlte/extras 9.22.0 -> 9.22.3: WebGPU variants + <Gizmo> fixes, drops the
  three-viewport-gizmo dependency (unused here; the app renders no threlte <Gizmo>).
Peer ranges: postprocessing 6.39.5 three >=0.168 <0.187, three-bvh-csg >=0.179,
threlte core/xr >=0.172/>=0.160 — all satisfied by 0.186.1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- svelte 5.57.1 -> 5.57.2: patch fixes (async deriveds on destroyed components, untracked
  derived reads retaining dependencies, {:else} after {:else} now a compile error, flushSync
  flushes pending work first).
- svelte-check 4.7.6 -> 4.7.7: modern CSS properties, tsconfig "include" aligned with tsc.
  check-ratchet unchanged: 299 errors / 39 warnings = baseline.
- @lucide/svelte 1.48.0 -> 1.61.0: icon additions/revisions (icon art can shift; covered by
  the A/B screenshots).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- vitest 5.0.2 -> 5.0.5 (+ @vitest/mocker/spy 5.0.5): fixes GHSA-qxg6-89mg-rqv8 (code
  injection via unauthenticated manual mock registration) and GHSA-xp26-3454-mjgp (Windows
  backslash traversal past the mocker redirect allowlist) — the two open Dependabot alerts.
  `npm audit` = 0 vulnerabilities (was 2 moderate).
- vite 8.3.3 -> 8.3.5 (rolldown 1.2.15): build preload-helper fixes (waits for in-flight
  stylesheets, chunk import map hashing), css `%` in url(); the rest is bundled-dev only
  (not used here).
vitest: 206 files / 2572 tests, same as the base.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t 4.14.0

The exact pins stay exact (48 G2 pinned iwer/devui; axe pinned since 53).
- iwer / @iwer/devui 2.6.0 -> 2.7.0: hand poses/microgestures, run_sequence, and
  "spec-correct select and squeeze events". vr-iwer 36/0, vr-comfort-iwer 25/0,
  race-vr-shadows-iwer 13/0.
- @axe-core/playwright + axe-core 4.13.0 -> 4.14.0: label-content-name-mismatch is now ON
  by default, plus false-positive fixes. a11y-axe 12/0 (0 serious/critical nodes on every
  surface), union-53 18/0.
- prettier 3.9.9 -> 3.9.10 (patch).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
0.x minor (outside ^0.8.0), taken on proof (QUESTIONS-deps-1 Q1): libwebrtc M98 -> M106,
fixes a segfault/abort at process exit (destructors ran after V8 shut down) and too-early
GCs; RTCRtpTransceiverDirection "stopped"; spec addIceCandidate end-of-candidates.
Proof: tools/agent npm test 16/16; an in-process two-peer data-channel loopback (offer/answer,
ping -> pong) on 0.8.0 and 0.10.0; e2e agent-bridge 5/0 (real browser session <-> the Node
agent over WebRTC: approve, create/move/color/delete land, Users list).
npm audit (tools/agent) = 0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…248)

1.64.0: WebMCP, video fps/style, locator.within(), page.getByRef(). Breaking changes checked
against this repo: device descriptors now forward `screen` (no `devices[...]` used here),
JSX/snapshot runner changes (no @playwright/test runner here), elements in hidden iframes
count as hidden (the battery covers it). 1.64.1: Chromium 155+ preflighted cross-origin
redirect abort with routes registered (fixed).
Hosts: chromium-1248 installed on the Deck beside 1234/1243; the PC run-server (:39001) and
tp-lanes serve 1.64.1 beside 1.62.1/1.63.0 (mux-pc-host, 2026-10-23).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nel for pose streams

- fastChannel.js (leaf): a negotiated {ordered:false, maxRetransmits:0} data channel on each
  PeerJS connection's own RTCPeerConnection (id 7, no extra signaling), opened only when the
  other side advertises `fc: 1` in the modules handshake + a `fastchan` hello down the same conn
- every pose message (camera, vrhands, move) carries `ps`, one monotonic counter per sender;
  receivers drop a pose older than the newest applied for that (sender, key) — on both channels
  and inside batch envelopes, so a late streamed move never overwrites the committed one
- trailing keyframe: a stream's last pose is re-sent reliably (same ps) once the key goes
  quiet for 250 ms, to the peers that got it unreliably
- streams marked fast: avatar camera (not when its extras change), VR hands (not on a rep
  switch), gizmo drag, physics bodies, VR held objects (not the release), play-mode grabs,
  possess rides; everything else stays on the reliable conn
- graceful fallback: older peers / closed channel / >12 KB messages go reliable; kill switch
  safeStorage fastChannel=off; `fastchan` on the cloudHooks ALWAYS_ALLOWED floor
- debug hook __stores.fastChannel (links, stale filter, keyframes, pose tap for the loss e2e)
- unit: tests/unit/fastChannel.test.js (18)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- manifest: share_target (POST multipart `files`: images, glb/gltf, tpscene), file_handlers
  (.tpscene, .glb, .gltf → /), launch_handler focus-existing
- static/sw.js: receives the share POST (a static site has no server), parks the files in
  the `tp-share-inbox` cache and 303s to /?shared=<n>; everything else is still a no-cache
  passthrough
- src/lib/launchInbox.js: reads the inbox ONCE (cache deleted, ?shared= stripped) and takes
  launchQueue file launches; routes .tpscene → open (Sidebar Load path), models → import
  into the current scene (companions ride along), lone images → Explorer Library + "Show in
  Library"; waits for the scene group AND the restore-session answer first
- autosave: restoreSettled() — resolves once the boot's restore question is answered
  (no snapshot / non-empty / auto-restored / Restore finished / Dismiss)
- tests/unit/launchInbox.test.js (14): manifest keys, the real sw.js in a fake worker scope
  (park + 303, text-only, replace, passthrough), the worker → app round trip, classifier

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…r a cap

- assetCacheCore.js (zero-import leaf): url keys (+ a version tag for build-bundled files),
  derived keys (source hash + kind + settings, field order irrelevant), the index shape and
  its normalizer, usage (a shared blob counted once), the LRU eviction plan, orphans
- assetCache.js: one blob per SHA-256 in OPFS (tp-asset-cache/blobs) + index.json; the same
  model on CacheStorage where OPFS cannot write; a plain fetch where neither exists. Reconciles
  orphan blobs / missing blobs on open; index flushed on a 400 ms debounce and on pagehide
- kinds wired: pack files (packCache.js is now a url-keyed view over the cache, same API; 39 P5's
  CacheStorage copies are migrated once and that cache deleted), bundled skies (hdriRuntime,
  keyed by the build version), CC0 downloads (cc0Fetch.fetchBytes), converted textures (the
  import optimizer's output keyed by source hash + options + version), pack anim refs
- navigator.storage.persist() asked once, from a toast with the reason, the first time something
  is cached (assetCache:persistAsked); never on boot, never again
- never the app shell: only content urls reach it
- unit: tests/unit/assetCacheCore.test.js (13); place-drag-cache reads the store through
  packCache.cachedPackBuffer instead of CacheStorage

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…uites

- Settings ▸ Storage (StorageSettings.svelte, Workspace group): what the cache holds by kind,
  Clear, the size limit (256 MB / 512 MB / 1 GB / 2 GB, LRU), the persistence state with
  "Ask the browser", and the Storage breakdown button
- Storage breakdown: a "Cached downloads" category (skies, converted textures, downloaded
  files), one row per kind, reclaimed through the cache's own delete
- e2e: asset-cache-opfs (second load served from OPFS with no request, persist asked once,
  no shell in the index, Settings Clear, the 39 P5 migration), fast-channel-loss (p95 pose gap
  at 0/5/10 % loss before/after through a WebRTC loss shim), fast-channel-fallback

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ngs the view back

The player starts where the editor camera is: X/Z under it, the feet on the first solid
surface straight below (scene meshes on the shared BVH trees, then the physics ground
plane; hidden objects, sensors, water and reference images do not count), facing the
view's heading. Nothing below → the scene's spawn; no spawn either → the camera's eye.
A session-only override that currentSpawn() returns first, so desktop Play, the VR
walker, api.respawnPlayer and the game shell all honour it for that round; leaving Play
clears it and restores the editor camera, its orbit target and the selection.

Entry points: Ctrl+Shift+Enter, a row in Play's right-click / phone long-press menu, an
optional toolbar roster button (Customize), phone More ▸ Tools & View, the viewport
menu's Tools, and the VR radial Tools ▸ Play here / Back to edit (Interact from where
you stand; Edit restores the exact reference space and world grab).

Height follows the scene's locomotion: desktop Play is grounded by default (37 R24 pins
the eye), and the VR walker stands on real surfaces only while a simulation runs.

Tests: unit playHere (the spawn raycast through a real three scene), e2e play-here
(desktop + phone N6 folded) and play-here-iwer (IWER, applied-offset spy).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rop menu (untested)

underlays.js (LOCAL per scene in IndexedDB; Share with session = underlay /
underlaydelete / getunderlays, owner-only rows, ROOM_SCOPED, request on the floor,
validated), UnderlayLayer (under the world rig, outside sceneObjects, on the editor
helper layer, hidden in Play/Interact, no-op raycast), UnderlayWindow (opacity, size,
height, ground / facing the view, share, remove) + the image-drop 'Use as reference'
menu, viewport Tools rows. Viewport/room thumbnails now render without the helper
layer. NOT yet run: svelte-check ratchet after the type fix, the underlay e2e suite.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AlexZ005 and others added 27 commits October 25, 2026 13:18
…llback counts only its own poses; fix-live survives a cloud build

- launch-inbox: the 2x2 PNG had a broken IDAT (the Library import never decodes, Use as reference does) —
  a valid one; the toast button is a DOM click; the check prints why.
- launchInbox: Use as reference says so when there is no scene view yet, and records the outcome
  (lastLaunch.reference) for the debug hook.
- fast-channel-fallback: the tap counts the suite's own poses only (the app's camera loop sent a 61st).
- fix-live: the viewer role is re-asserted inside each AI call (a cloud build's roles plugin re-publishes
  rolesInfo); the re-dial after a graceful leave waits for the idle pill and taps a squeezed dot.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…runner speed)

scatter-sync phase 1: the four two-peer scatter suites only fail on GitHub's runners;
this lets them be reproduced on a fast box (throttle 2 + taskset 4 cores did it).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… reach them

broadcast skipped a conn that was not open, so everything the host did between
approving a joiner and the dial-back opening (15 s on a throttled run) was dropped;
the joiner only converged if its full-state request was answered later, through the
slow GLTF reply (+24.6 s measured: the scatter suites' 'B has the terrain' on GitHub).
The host now holds room-scoped broadcasts for a peer in approvedDialBacks (bounded:
300 messages / 120 s, a leaf with unit tests) and flushes them, re-gated, after that
conn's handshake. Counterfactual: e2e dialback-sync, red on v1.43.2, green here.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…imum); E2E_LONGTASKS logger

On a GitHub-speed runner the approval dial-back opened 15 s after approval, past the
fixed 9 s settle, so a suite's first edit raced a negotiating conn. connect() now also
waits (bounded 45 s, never failing) until both ends hold an open conn. E2E_LONGTASKS=<ms>
prints main-thread tasks over that length, to tell a hung click from a slow one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… (h.eventuallyAfter)

On a GitHub-speed runner a two-peer page renders a frame every 1.5-2.5 s (E2E_LONGTASKS,
CPU x2), so B dispatches A's messages long after A sent them and fixed 10-15 s windows
went red with nothing lost ('B paints at 256 too'). h.caughtUp sends A's ordinary clock
ping to B and waits for B's pong: on the ordered conn that is the moment B has dispatched
everything before it (capped 120 s, says so if not). Each check keeps its own window,
measured from there, so a lost message is still a red.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ys cancelled (#468)

* [docs] deps-auto 3g: pc-battery — PC_BATTERY_WRAP has to be quoted in a systemd unit

Unquoted, Environment=PC_BATTERY_WRAP=e2e-slot --exclusive ... is split at the spaces (found
installing the timer on the desktop).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2j: shards balance on the median runner time of runs 5-10 (16 shards; replayed slowest shard 49 -> 35-38 min)

- refresh --results is repeatable and takes the median per suite: a suite's time moves by half
  between runs on a shared runner, and the 15-shard split fitted to run 9 alone put run 10's
  shards anywhere from 21 to 50 min
- battery.json seconds from runs 5-10 (required + optional passes); replaying runs 8 / 9 / 10 on
  the new split: max shard 38 / 37 / 35 min (was 49 / 38 / 49)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2k: battery only where it tests something; optional set by hand

- deps-policy.cjs: new `battery` output = the PR moves the root app's npm packages and is not a major
- deps.yml: perf budget + plan/e2e only when battery=true (Actions, tools/agent and majors get ci.yml alone);
  a major's deps-gate fails on purpose ("not tested here"); the optional set runs on workflow_dispatch only
- why: Dependabot's first round after 1.43.0 opened 9 PRs at once = ~380 jobs on the account's 20-job cap,
  queueing the v1.43.0 release job and every lane's CI for hours (runs cancelled, re-run after this)
- RELEASING.md + depsCi test (9/9); check-ratchet 299/39 = baseline; actionlint clean

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2l: a cancelled deps run stays cancelled

- confirm / e2e-optional / optional-report / deps-gate used always(), which also runs after a cancel: cancelling
  Dependabot's 9 first-round runs still started their optional sets (18 shards each); now !cancelled()

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2m: three of 51's scatter suites optional on the GitHub runner; medians from runs 5-12

- run 12 (#468, released 1.43.2 app code, CI-only diff) was the scatter suites' first run on the runner:
  scatter-select + scatter-convert red twice (shard + fresh confirm runner: "B has the terrain" never true),
  scatter-sources at the 600 s suite timeout twice (all its checks green until then) -> optional with reasons
- scatter-edit was flaky (green on the confirm runner) and stays required
- refresh --results over runs 5-12 (one durations file per run): 16 required shards ~35 min each

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2n: scatter-edit optional on the GitHub runner too

- run 13 (#468): scatter-edit red on its shard and both confirm tries, the same 'B has the terrain' as
  scatter-select/convert in run 12 (where it was green on confirm); scatter-surface flaky, stays required

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…back queue

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…k to required

GitHub probe (scatter-sync-probe run 40191960730): 'B has the terrain' green on all three
runners, runners 1+2 all green; the slowest runner's reds were all a real click timing out
at 30 s with no main-thread task over 8 s on the clicking page — each input event waits for
a frame ack, and a frame took seconds there. h.PATIENT_CLICK_MS (90 s, E2E_CLICK_MS) keeps
every actionability check and only waits longer. scatter-colliders (optional since deps-auto
run 5) passed on runners 1+2 and goes back to required.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tach, not only the click)

deps-auto run 16 timed out scrollIntoViewIfNeeded ('waiting for element to be stable', i.e. two
frames with one box) before the click at :136. Measured (probe-surface-jank, CPU x2, two pages):
A's frame is ~2.2 s with one sphere idle, the same after the selection opens the Inspector
(no extra canvas, no long task beyond the frame), 2.45 s after Add scatter; the click needs
~13 frames = 28.7 s. A slow runner, not a jank at that click — the overload guard's own rule
(a light scene on software GL is slow, never paused).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…l look gets 90 s; runner estimates

Confirm probe (run 40196033046, three slower runners): select/convert/edit green x3; the rest
were load, not sync. scatter-colliders' drop() sampled stillness on wall time while the step
accumulator catches up <= 8 substeps a frame (30 s wall ~ 1 s simulated on multi-second frames):
each sample now spans 500 ms AND 8 rendered frames, capped at 240 s, and the watcher compares
after a wire barrier. 'A draws the crates' (a local look load) 20 s -> 90 s. battery.json runner
estimates for the six suites set from the probes (they also set battery.cjs' per-suite cap,
3x the estimate, min 600 s, which cut scatter-sources/-colliders off mid-run).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… runners; cap = 3x)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ettings ▸ Storage, poses on the fast channel); battery-snapshot copies quarantine

- vr-radial-menu, phone-bar-42, toolbar-edit-positions ("never Play" no longer matches "Play here"),
  settings-shell (Storage after Explorer): the 60 Q1/Q6 additions.
- avatars-rigged, net-stress: camera/vrhands ride the fast channel between two 1.44 peers — count the
  fast arrivals through fastChannel's pose tap as well as the reliable conn.
- ai-flow-physics prints the tool errors it finds.
- battery-snapshot.sh: battery.cjs requires scripts/ci/quarantine.cjs + tests/e2e/quarantine.json
  (test-health-1) — a base snapshot crashed at load without them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
peerHandler: #474's broadcast shape (an approved joiner's opening dial-back holds room content,
withholdFrom/sendTo shared with the flush) carries 60 Q2's fast-channel attempt and keyframe
bookkeeping inside it; sendReliableTo (the trailing keyframe) re-gates through withholdFrom.
battery.json: the union's optional notes and estimates + #474's dialback-sync and its six scatter
estimates; the scatter-* optional keys removed (scatter-colliders was the one left).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…asks for a dynamic Pebble (fix-live F-4)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…4.0 changelog line for #474

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* [docs] deps-auto 3g: pc-battery — PC_BATTERY_WRAP has to be quoted in a systemd unit

Unquoted, Environment=PC_BATTERY_WRAP=e2e-slot --exclusive ... is split at the spaces (found
installing the timer on the desktop).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2j: shards balance on the median runner time of runs 5-10 (16 shards; replayed slowest shard 49 -> 35-38 min)

- refresh --results is repeatable and takes the median per suite: a suite's time moves by half
  between runs on a shared runner, and the 15-shard split fitted to run 9 alone put run 10's
  shards anywhere from 21 to 50 min
- battery.json seconds from runs 5-10 (required + optional passes); replaying runs 8 / 9 / 10 on
  the new split: max shard 38 / 37 / 35 min (was 49 / 38 / 49)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2k: battery only where it tests something; optional set by hand

- deps-policy.cjs: new `battery` output = the PR moves the root app's npm packages and is not a major
- deps.yml: perf budget + plan/e2e only when battery=true (Actions, tools/agent and majors get ci.yml alone);
  a major's deps-gate fails on purpose ("not tested here"); the optional set runs on workflow_dispatch only
- why: Dependabot's first round after 1.43.0 opened 9 PRs at once = ~380 jobs on the account's 20-job cap,
  queueing the v1.43.0 release job and every lane's CI for hours (runs cancelled, re-run after this)
- RELEASING.md + depsCi test (9/9); check-ratchet 299/39 = baseline; actionlint clean

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2l: a cancelled deps run stays cancelled

- confirm / e2e-optional / optional-report / deps-gate used always(), which also runs after a cancel: cancelling
  Dependabot's 9 first-round runs still started their optional sets (18 shards each); now !cancelled()

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2m: three of 51's scatter suites optional on the GitHub runner; medians from runs 5-12

- run 12 (#468, released 1.43.2 app code, CI-only diff) was the scatter suites' first run on the runner:
  scatter-select + scatter-convert red twice (shard + fresh confirm runner: "B has the terrain" never true),
  scatter-sources at the 600 s suite timeout twice (all its checks green until then) -> optional with reasons
- scatter-edit was flaky (green on the confirm runner) and stays required
- refresh --results over runs 5-12 (one durations file per run): 16 required shards ~35 min each

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2n: scatter-edit optional on the GitHub runner too

- run 13 (#468): scatter-edit red on its shard and both confirm tries, the same 'B has the terrain' as
  scatter-select/convert in run 12 (where it was green on confirm); scatter-surface flaky, stays required

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2o: auto-merge in its own job with contents: write

- #460 (actions, class auto) went green through the gate on the 2k deps.yml, then the gate's auto-merge step
  failed: 'Resource not accessible by integration (enablePullRequestAutoMerge)' (it needs contents: write)
- a separate automerge job after a green deps-gate holds contents + pull-requests write and checks nothing
  out, so the gate (which runs the PR's code) stays contents: read

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2p: battery.cjs run with no --deadline runs every suite

- the deadline was Date.now() + 0, which is truthy, so '|| Infinity' never applied: by hand (no flag) every
  suite after the first came out notrun. CI always passes --deadline, so the workflows were unaffected.
  Found by lane scatter-sync.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2q: scatter-surface optional on the GitHub runner

- red on its shard in runs 13, 14 and 16 (#473 run 16: shard + both confirm tries): the Add scatter click
  times out (locator.click 30 s) - a different failure from the late-joiner terrain race

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2r: knock-physics + env-preset-broadcast optional on the GitHub runner

- the two required suites that fail on their shard about half the time (runs 10-17): knock-physics (shard red
  in 8, 9, 12, 14, 17; run 17 also both confirm tries, '2.3 (premise) B sees the ball parked') and
  env-preset-broadcast (shard red in 10, 12, 13); at ~50% a run loses all three tries ~1 in 8, so each
  alone turned deps-gate red every few runs. The rest of the flaky set is ~25% or less

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…terial-presets-big keeps peerjs's chunked flag

Remove only cut the host's own link: the roster applier unions every relayed userdata and a
'hosts' mesh-fill dials whoever a third peer still holds, so B put C straight back (fix-live
F-5 'Remove drops C' / 'cannot dial back in' red 2 of 3 on the preview). Both now skip
removedPeers. The suite's send counter dropped send()'s second argument, so peerjs re-chunked
its own chunks until the stack overflowed (a test-made page error).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s skipped (#475)

* [docs] deps-auto 3g: pc-battery — PC_BATTERY_WRAP has to be quoted in a systemd unit

Unquoted, Environment=PC_BATTERY_WRAP=e2e-slot --exclusive ... is split at the spaces (found
installing the timer on the desktop).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2j: shards balance on the median runner time of runs 5-10 (16 shards; replayed slowest shard 49 -> 35-38 min)

- refresh --results is repeatable and takes the median per suite: a suite's time moves by half
  between runs on a shared runner, and the 15-shard split fitted to run 9 alone put run 10's
  shards anywhere from 21 to 50 min
- battery.json seconds from runs 5-10 (required + optional passes); replaying runs 8 / 9 / 10 on
  the new split: max shard 38 / 37 / 35 min (was 49 / 38 / 49)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2k: battery only where it tests something; optional set by hand

- deps-policy.cjs: new `battery` output = the PR moves the root app's npm packages and is not a major
- deps.yml: perf budget + plan/e2e only when battery=true (Actions, tools/agent and majors get ci.yml alone);
  a major's deps-gate fails on purpose ("not tested here"); the optional set runs on workflow_dispatch only
- why: Dependabot's first round after 1.43.0 opened 9 PRs at once = ~380 jobs on the account's 20-job cap,
  queueing the v1.43.0 release job and every lane's CI for hours (runs cancelled, re-run after this)
- RELEASING.md + depsCi test (9/9); check-ratchet 299/39 = baseline; actionlint clean

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2l: a cancelled deps run stays cancelled

- confirm / e2e-optional / optional-report / deps-gate used always(), which also runs after a cancel: cancelling
  Dependabot's 9 first-round runs still started their optional sets (18 shards each); now !cancelled()

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2m: three of 51's scatter suites optional on the GitHub runner; medians from runs 5-12

- run 12 (#468, released 1.43.2 app code, CI-only diff) was the scatter suites' first run on the runner:
  scatter-select + scatter-convert red twice (shard + fresh confirm runner: "B has the terrain" never true),
  scatter-sources at the 600 s suite timeout twice (all its checks green until then) -> optional with reasons
- scatter-edit was flaky (green on the confirm runner) and stays required
- refresh --results over runs 5-12 (one durations file per run): 16 required shards ~35 min each

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2n: scatter-edit optional on the GitHub runner too

- run 13 (#468): scatter-edit red on its shard and both confirm tries, the same 'B has the terrain' as
  scatter-select/convert in run 12 (where it was green on confirm); scatter-surface flaky, stays required

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2o: auto-merge in its own job with contents: write

- #460 (actions, class auto) went green through the gate on the 2k deps.yml, then the gate's auto-merge step
  failed: 'Resource not accessible by integration (enablePullRequestAutoMerge)' (it needs contents: write)
- a separate automerge job after a green deps-gate holds contents + pull-requests write and checks nothing
  out, so the gate (which runs the PR's code) stays contents: read

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2p: battery.cjs run with no --deadline runs every suite

- the deadline was Date.now() + 0, which is truthy, so '|| Infinity' never applied: by hand (no flag) every
  suite after the first came out notrun. CI always passes --deadline, so the workflows were unaffected.
  Found by lane scatter-sync.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2q: scatter-surface optional on the GitHub runner

- red on its shard in runs 13, 14 and 16 (#473 run 16: shard + both confirm tries): the Add scatter click
  times out (locator.click 30 s) - a different failure from the late-joiner terrain race

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2r: knock-physics + env-preset-broadcast optional on the GitHub runner

- the two required suites that fail on their shard about half the time (runs 10-17): knock-physics (shard red
  in 8, 9, 12, 14, 17; run 17 also both confirm tries, '2.3 (premise) B sees the ball parked') and
  env-preset-broadcast (shard red in 10, 12, 13); at ~50% a run loses all three tries ~1 in 8, so each
  alone turned deps-gate red every few runs. The rest of the flaky set is ~25% or less

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* [fix] deps-auto 2s: automerge runs after a green gate even when the battery was skipped

- #460 on c6dda92: run green (scope auto, ci green, deps-gate + pc-battery SUCCESS) but automerge SKIPPED:
  its implicit success() also checks the gate's own needs, and perf/plan/e2e are skipped for every PR
  without the battery. Now: !cancelled() && needs.gate.result == 'success' && class auto && base deps

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Bumps the actions group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `4` | `7` |
| [actions/setup-node](https://github.com/actions/setup-node) | `4` | `7` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `4` | `8` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `4` | `7` |
| [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) | `2` | `3` |
| [softprops/action-gh-release](https://github.com/softprops/action-gh-release) | `2` | `3` |



Updates `actions/checkout` from 4 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v4...v7)

Updates `actions/setup-node` from 4 to 7
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](actions/setup-node@v4...v7)

Updates `actions/download-artifact` from 4 to 8
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](actions/download-artifact@v4...v8)

Updates `actions/upload-artifact` from 4 to 7
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v4...v7)

Updates `dependabot/fetch-metadata` from 2 to 3
- [Release notes](https://github.com/dependabot/fetch-metadata/releases)
- [Commits](dependabot/fetch-metadata@v2...v3)

Updates `softprops/action-gh-release` from 2 to 3
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](softprops/action-gh-release@v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/setup-node
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: dependabot/fetch-metadata
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: softprops/action-gh-release
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
battery.json: deps' runner medians and optional reasons; scatter-surface takes the runner's 238 s;
the scatter-* optional keys go after #474 (orchestrator) — knock-physics and env-preset-broadcast
stay optional until green twice on the runner.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
net-drop on the deployed preview lost ONE direction's edits after a heal: one end closed its link
(F-3's ICE grace, or the drop) and redialled, while the other end's conn still read open - a close
never crosses a dead link - so it refused the inbound heal ("the stable outgoing conn stays
preferred") and kept sending, and answering the heal's full-state requests, into a conn nobody
reads. A healed handshake now says `relink` before anything that is answered; the receiver whose
conn still reads open adopts the conn it came on, closes the stale one and re-syncs both ways.
`relink` is on the capability floor (transport, no content).

net-relink manufactures the zombie (higher id: reads open, swallows sends, hears no close; the
lower id closes its end): 9/0 twice; counterfactual without the relink send 6/3 (the zombie is
kept, Z's edits and full-state reply never reach L). fix-live 46/0, dialback-sync 7/0,
fast-channel-loss 32/0 with it; net-drop/net-reconnect red alike on base and relink snapshots
(box load ~19). Battery: net-relink 97 s, optional until a runner has seen it green.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Release 1.44.0 — Quick wins (roadmap 60)
…P again)

three's prebuilt basis_transcoder.js (basis_universal v1_50_0, Emscripten 3.1.64) builds its embind
invokers with newFunc(Function, args). The build's CSP has no 'unsafe-eval' and KTX2Loader's blob:
worker inherits it, so every KTX2 texture died with an EvalError (since 1.43.1).

- static/basis: same source + flags, plus -s DYNAMIC_EXECUTION=0 -s EMBIND_AOT=1 (basis_universal's
  own opt-in NO_DYNAMIC_EXECUTION build, b15211474d). wasm byte-identical, JS 57,529 -> 66,534 B.
- scripts/build-basis-transcoder.sh reproduces it; --reference reproduces three's two files byte for
  byte (sha256 8478b5b6… / 6cf17dc8…), which pins the toolchain.
- scripts/no-eval-scan.cjs (acorn): finds eval / the Function constructor used as a value, which a
  grep for "new Function" misses. tests/unit/basisNoEval.test.js runs it over static/**/*.js
  (red on v1.44.0's transcoder, green now).
- static/basis/README.md: provenance, sizes, sha256s, how to rebuild after a three update.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…LOG line

- csp-build (built app, serve-build): transcode an ETC1S and a UASTC KTX2 through the app's KTX2Loader
  under the build's CSP. Built-app A/B (pc slot): fix 8/0; v1.44.0's transcoder 7/1 (the new check,
  "no answer in 30 s"). ktx2-twins 20/0, export-presets 19/0, code-popout 40/0 on the fixed build;
  production 1.44.0: ktx2-twins 10/4 then timeout (EvalError), export-presets hangs.
- no-eval-scan: comparisons (`x.constructor === Function`) and `Function.prototype` are not eval.
- CHANGELOG: "Compressed (KTX2) textures load again" under Unreleased (the integrator versions it).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@AlexZ005

Copy link
Copy Markdown
Collaborator Author

Shipped in 1.45.1 via cherry-pick (release/1.45.1, tag v1.45.1); feat/70 gets it by merging the tag.

@AlexZ005 AlexZ005 closed this Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant