Skip to content

1.19.0: Doors that open, scenes that load, games that switch (round 33) - #264

Merged
AlexZ005 merged 55 commits into
mainfrom
feat/1.19
Oct 2, 2026
Merged

AlexZ005 merged 55 commits into
mainfrom
feat/1.19

Conversation

@AlexZ005

@AlexZ005 AlexZ005 commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator

Round 33 ("packs that move, scenes that load, games that switch") for core 1.19.0.

Merged: #258 editor-ui · #257 lod-editor · #259 anim-core · #261 scene-load · #263 scene-switch · #260 games · #255 untangle-core · #262 scenes, plus integrate fixes:

  • the General-tab card titled "Architecture shell" rebuilt from the kits (a remote: false def field keeps the greybox as the offline seed)
  • four suites taught about the union: scene-switch-clear (a sliced load), animated-models (compare clip clocks; three pages on the GPU), scene-load (click exactly "Restore")
  • the phone quality toast says "Use full quality" (it sat beside the session's "Restore" on every phone load)
  • CLAUDE.md round-33 entries + gotchas; svelte-check baseline ratcheted to 332/47

Verification: vitest 464 · svelte-check 332/47 · build green · ~75 core suites + 31 modules flights green on the union · flicker probe 222/222 over ten mesh packs · preview https://preview-1-19.theprototype.pages.dev proven: seven games 211/0, round-33 items 58/0.

Per-item table and the owed-on-device list: the 33-integrate handover and the cloud roadmap-33 master.

🤖 Generated with Claude Code

AlexZ005 and others added 30 commits October 1, 2026 19:54
- vrControls kept ONE grab slot. A second hand's grab overwrote it, so the first
  hand's piece stayed in its kinematic physics hold with nothing updating or
  releasing it: it hung in mid-air until a hand took it again (Towers report).
- grabs[] is indexed by controller slot; each hand updates and releases its own,
  a released body is dynamic at once. Two hands on the SAME object still scale it
  in Edit (and still do nothing extra in Interact).
- vrGrabbedHand reads 'both' while both hands hold; the two stick gates honour it.
- vrGrabbedUuids(): the knock probe skips everything the hands hold.
- an input-source rebind now RELEASES what the hands held instead of dropping the
  grab with the body still kinematic.
- new suite vr-two-hand-grab (19, fake XR + a running rapier sim).
  CF: clearing the other hand's slot on grab (the old single-slot shape) -> 6 red
  (1.1, 1.2, 2.2, 2.3, 2.5 hold user/bodyType 2 = the frozen crate, 2.12).
- vr-grab 7, vr-grips-by-mode 18, vr-source-rebind 5, knock-physics 77,
  vr-sleeve 29 green. svelte-check 333/47 unchanged.

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

The user: "opened waves game, then towers - music from waves stays and the gun,
but only objects from towers load". A game module is a SCENE SCRIPT; when its
scene goes away, whatever it registered for its own game must not reach the next.

- sceneScope.js (a LEAF): the modules the scene was USING when it was cleared
  (noteSceneLeaving, called first thing in clearSceneLocal) and the next scene
  does not use are LEFT BEHIND; ownerInScope(owner). A fresh install in a blank
  scene is in scope (nothing was left behind), so every existing flow is unchanged.
- gameShell: levels / How to play are kept PER OWNER and the newest in-scope
  registration shows - a kept Waves' levels do not appear in Towers' menu, and
  come back with no re-registration when Waves is open again. Restart hooks
  carry an owner and a left-behind one does not run.
- gameSettings: a left-behind module's settings rows leave the Settings page.
- gameMusic: the track is OWNED (api.music passes the module id). A left-behind
  owner's track stops and it cannot start one; api.music.stop only stops the
  module's own track; the module teardown journal now stops its music (it never
  did - "music from waves stays" after an unload too).
- playSettings: a left-behind module's runtime spawn is ignored.
- towers: onSceneClear unregistered nothing (it reset wasActive, so the frame
  edge saw false -> false) - Towers' twelve levels stayed in the next game.
- api.inScene(): false once a switch left the module behind, so a game module
  can stand down its own drawing (the gun) - feature-detected.

vitest sceneScope 12/12; counterfactual (ownerInScope -> true): 8 red.
vitest 390/390, svelte-check 333/47 (baseline).

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

The teardown audit of every SDK registration (music, game.*, effects, input,
spawn, locomotion):
- playPublishers: a KEPT module's scene-root group still carries its
  userData.play, so Untangle's free cursor + world grab became the next game's.
  A group whose owner module a scene switch left behind publishes nothing.
- api.followCam journals its stop: a module unloaded mid-follow left the camera
  chasing its object.
(Already journalled and unchanged: input claims + bindings, possess, setSpawn,
hud rows, toolboxes, transport events, frame/click/drop handlers, scene-root
groups; effects bursts and the announce banner clear with the scene.)

vitest sceneScope 13/13; counterfactual (filter removed): the play-contract test red.

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

- scripts/scene-load-trace.cjs: a fresh phone context per scene (412x915 isMobile @2.6,
  real GPU, CPU throttle 6), the Templates open path and autosave -> reload -> Restore,
  each reporting wall time, PerformanceObserver long tasks, the longest rAF gap, the CPU
  profile's top self time, app call chains, and the three longest tasks broken down.
- Baseline (c7018be): Castle Courtyard Restore = ONE 89 s task (91 s frozen), open 525 ms
  tasks / 2.1 s frozen; Tavern Restore 84 s; Forest gets NO Restore at all (its autosave
  is over the 50 MB cap). Table in the 33-scene-load handover.

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

- sceneLoader.js (leaf): slice(job) yields when the 10 ms budget is spent and throws
  LoadCancelled at a clean point; schedule(fn) runs a burst of continuations a slice at a
  time; ONE load job (sceneLoad store) - a new load supersedes the running one; cancel
  hooks; throttledPoke (250 ms) for the build loop.
- sessions.applySession: the object loop is time-sliced with progress, kit stubs refill at
  once (counted when they land), every await re-checks the job, Cancel clears what the
  load added (replicated like the load), a load interrupting one mid-build writes no
  "Backup before" of the half scene.
- packRefs: a refill's clone/attach runs through schedule() (every copy of a piece used to
  attach in the one task that resolved its template); warmTemplate uploads textures one
  per slice and compileAsync's the programs against the live scene before any copy draws;
  refills poke at most every 200 ms; fingerprint checksums cached per geometry by buffer
  versions; packRef.box (root-frame bounds, additive) drawn as ONE scene-root InstancedMesh
  of grey placeholder blocks while a piece is on its way.
- App.svelte debug hook: + sceneLoader (220/220/220).

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

- SceneLoadBar.svelte in the toast stack's first slot (the spectator banner's spot, so
  arriving toasts never shove it): name, count, a progress bar (indeterminate while
  preparing/reading), Cancel while objects are being BUILT; once only kit models are still
  arriving it keeps counting without Cancel. Non-modal, shown after 250 ms so a small
  scene does not flash it. Ids #scene-load-bar (data-done/total/phase), #scene-load-cancel.

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

- autosave writes pristine kit pieces as STUBS (packRefs.parkPackPieces: hollowed for the
  GLTF export and put back on the exporter's afterParse hook, before the async encode can
  let a frame draw a hollow castle). Castle Courtyard's snapshot 51 MB -> 86 KB; Forest
  Clearing gets crash recovery again (it was over the 50 MB cap).
- applyRestore: time-sliced with progress ("Restoring your last session"), the wire copy
  built only when a peer is connected (stubs for pristine pieces) - every child's toJSON
  re-encoded its textures as PNG data URLs for nobody, the 89 s task; Cancel clears and
  re-offers the prompt; superseded by another scene cleanly.
- The debounced autosave waits out a load (saveNow does not).
- tests/e2e/scene-load.test.cjs (CPU x6 phone): open + restore with no long task > 200 ms,
  the bar counts, UI answers mid-load, supersede, cancel, stub snapshot, edited piece kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…); every level + level tabs in the VR pause menu

Untangle's Quest report (roadmap 33): "I can make bigger/smaller the globe but cannot use up/down
on stick to move it further/closer ... just as in edit mode for objects", and "main menu do not
show in VR levels to choose and tab to switch between flat/globe".

G2 — the stick reels what you hold, the world included:
- vrControls: the one-hand world PAN (a right grip on scenery/air in Edit, or in Interact when
  play.locomotion.worldGrab) now reels the world on that hand's stick Y with Edit's own object
  reel (grabStickAdjust): forward pushes it away along the hand's ray, back pulls it in, from
  the distance to the gripped spot (panReach). worldReelStep is pure + exported.
- api.claimInput('sticks'): BOTH VR sticks' navigation stands down (left-stick move, right-stick
  snap/smooth turn + teleport) for a module that reads the sticks itself (Untangle's held globe:
  Y reels, X scales). claimInput now answers true for a scope this core knows (feature detect).

G3 — the pause menu's Levels page in a headset:
- shellPanelDraw drew list.slice(0, 20): Untangle's levels 21-30 were unreachable in VR. Now a
  6-column grid as tall as fits (pages with arrows beside Back beyond that); <= 20 levels and no
  tabs keep the old 5 x 4.
- addSetting({..., type: 'choice', onLevels: true}): the choice is also drawn as TABS above the
  Levels grid, desktop (GameShellMenu, role=tablist) and VR (shell:tab hits). Untangle registers
  its Board (Globe / 2D board) that way.

Tests:
- vitest shellPanelDraw (new, 6) + gameShell (+3): CF — the old slice(0, 20) restored: 2 red.
- e2e vr-world-reel (new, 21/0 on 5278): pure step, Edit pan reel push/pull exactly by the reach
  growth, no drift at rest, Interact+worldGrab reel, the 'sticks' claim silencing snap turn and
  the left stick then releasing. fakeXR controllers do not follow the reference space, so the
  suite poses a spec-accurate right hand (re-derived on every setReferenceSpace).
  CF: on the feat/1.19 base the suite is red (no worldReelStep, claimInput answers undefined).
- held: game-shell 66/0, vr-game-panel 36/0, vr-nav-suppression 5/0, vr-grips-by-mode 19/0,
  vr-laser-panels ALL PASS (SCENES_DIR = the preview-1-18 scenes).
- svelte-check 333/47 (= baseline), vitest 387/387, check:storage clean.
- modules flights on this branch: untangle-vr2 94/0 (incl. S: left X -> Levels -> Board tabs ->
  a level, through the panel's press path), untangle-vr 98/0, untangle 71/0, untangle-drag 54/0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ons; menu layout audit (33 G5)

- The report: "Menu" and "Top strip" too close / outside the menu's border. Three
  240-stage-px footer buttons need 760 px while a cropped game menu board is 720 wide,
  so the row hung past BOTH edges (Menu at x -30, Top strip ending at 1110 on a 1080 px
  canvas) in all seven games.
- footerLayout(W, k, n): natural width when it fits, else the buttons share the board
  inside a side margin. fitLabel(): shrink a button label to fit, ellipsize only as a last
  resort and report it (collectOverflow -> vrGamePanelDebug().overflow). Used by the footer,
  the wrist card, the shell pages and a game screen's own buttons on the board.
- new suite game-menu-layout: the seven games (real .tpscene + real zips) x desktop HUD in
  every game state (inside the view, not half off a panel, 4 px apart), the pause menu's
  pages (inside the card, no overlap, no sideways scroll), the VR board per state + the
  shell pages on the board + the wrist (inside with a 10 px margin, 8 px apart, labels fit).
  BEFORE (HEAD's panel code): 7 red, exactly the reported footer (log + shots
  lanes-30/after-33/33-games/layout-before). AFTER run: see the handover.
- svelte-check 333/47 unchanged.

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

- P1 contract: a pack item row's `lods` place as the object's LOD group (userData.lod,
  normalizeLodGroup in lodGroupCore.js is the one boundary); levels are a RENDER-TIME
  per-mesh geometry swap matched by node name inside lod.js's bracket (registerLodPass),
  drawn with LOD0's own material; lazy (built when first needed, once per file);
  thresholds by screen size (25% / 10% defaults, 15% hysteresis); implicit group from
  the pack row for pieces placed before their pack had lods
- write path lodGroupActions.js: one props undo entry + one objectParameters 'lod'
  message (applier in commandsHandler, wireValidate shape, props kind in objectActions)
- P2 LodGroupPanel.svelte in Inspector: level bar with draggable edges (one entry per
  drag), Force LOD, Generate levels (meshopt in the worker), replace a level with an
  object / Explorer drop, select-a-level preview, per-level material override,
  gizmo on a level (lodLevelEdit.js + Scene isLodLevelProxy branches), Show LOD level
  overlay, cull; api.lod handle gains force()/levels()
- P3 VR props panel LOD row (readout + cycle Force LOD)
- docs: PACKS.md lods field, MODULES.md
- vitest lodGroupCore (22) green; svelte-check 333/47 = baseline
- e2e lod-group: 22/24 on the first run, both reds fixed (test premises + block
  writes now take effect the same frame); re-run, lod-panel and vr-props-panel NOT
  yet run (slots/memory) - see the handover

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…counter, VR select-then-grip (WIP, proofs running)

- E1: default bar = move/rotate/scale, Interact, Play, object list, node editor, Explorer,
  Animation; a stored record that is still a shipped default migrates (keeps posX/collapsed),
  a customized bar wins as saved and does not gain Animation (PROMOTED_DEFAULTS)
- E2: the Interact <button> cell drops classActive's click-focus ring (focus-visible only)
- E3: the play well's hover halves answer `*:hover`, not `p:hover` (the Interact button left
  two pill-coloured corners beside Play)
- Q1: Settings > Interface > Viewport "Show FPS + draw calls" (+ VR settings row): the one
  FpsCounter in any mode, calls amber > 120 / red > 150 (fpsMeter.callsTier); vrPerfStrip.js
  head-locked strip in a headset; sceneBudget.noteExternalFrame so XR frames drive the
  draw-call sampler (the window rAF stops in a session)
- E4 (VR half): a SELECTED scenery object (>= 4 m / around the head) is grip-held in Edit;
  unselected scenery still moves the world; one-time headset hint
- tests: vitest fpsMeter + vrGrip; e2e editor-ui-33 (new), controls-roster + vr-grips-by-mode
  updated — the e2e proofs were still running at this commit (see the lane handover)

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

- behaviorCore.js (pure, vitest 18): normalizeBehavior, the pose as a pure function of
  {on, at, from, n} + the session clock, nextState, frameSlabs/passageThrough (the doorway
  cut out of a one-piece frame, through the wall's depth)
- packBehavior.js: per-frame pose driver (rest pose in Edit whatever the shared state),
  a core click handler in moduleSDK's one dispatch (desktop Interact click, Play tap, VR
  laser/poke), proximity (1.5 m) and knock (VR hand >= 0.6 m/s) triggers, the `behavior`
  wire message (latest-wins on sessionNow), a kinematic box per moving node that follows
  the swing while a sim runs + the frame as custom slab colliders, a local Animation-panel
  preview (never sent/saved), the per-item sound
- animatedImports: NOTHING AUTOPLAYS on placement any more (the first clip used to start
  and loop); behavior roots skip the transport; spec (+ live state on the wire, + the
  lod-editor's userData.lod) ride objectfile, the save snapshot and the undo entry
- fileHandler: animated imports land at the drop point; opts.behavior from the pack row
- packs/Explorer/explorerDrop: the row's spec travels to placement; an "animated" badge on
  cards and list rows
- gameSfx: + door gate slide lever lid; wireValidate `behavior` shape; ROOM_SCOPED;
  vrGameInput treats behavior roots as clickable; api.behavior {list, state, trigger}
- suites: pack-behavior (new; test-door.glb built with gltf-transform), animated-models and
  animation-persist press play first now, game-audio expects 25 sounds

WIP at the budget checkpoint: pack-behavior ran 41 pass / 8 fail before the priming fix in
this commit. 7.2 (the first walk resolved on the plane tier: charController's lazy physics
import; primed now, not re-run). 7.6 (the reopen click on the frame post did not toggle)
cascades into 8.2/9.1/11.2/13.1/13.2 - next window's first job. Counterfactuals already
green: 7.7 (uncut frame = a wall when open), 2.3/12.1 (no autoplay), 14.1 (slow hand).
svelte-check 333/47 (= base), vitest 396/396, check-storage clean.

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

- PACKS.md: the behavior contract (types, triggers, the no-autoplay rule, shared state,
  frame + moving parts + collider follow, authoring notes, the Explorer badge)
- MODULES.md: api.behavior list/state/trigger and the five item sounds

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

- physics.js: in EDIT, in a GAME scene (state-bound HUD screen or play.simOnPlay), releaseBody
  PARKS the body (hold 'edit': kinematic at its object pose, skipped by the deviation detector)
  instead of handing it back to the sim with a throw; leaving Edit (Interact or Play) releases
  every parked body at rest. A plain physics playground keeps the P-A editor throw.
  Measured before: Towers 10/44 pieces flung ~47 m, Stars Room 26/49 drifting, the football
  rolling away once a game ran and the editor went back to Edit.
- new suite edit-move-games: the seven games, every object (cap 60) dragged through the gizmo
  events, fresh AND game-running; parked bodies released on leaving Edit; Interact release not
  parked (counterfactual). Waves' walking enemies are module-positioned mid-wave (noted, F5).
- editor-ui-33: E3 corner pixels chosen outside the FAB and sampled un-hovered; Q1 toggle
  pressed as an element click. controls-roster: the walk needs a third press past Interact.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…oser programs, sky first

- Controls.svelte: under the virtualisation threshold the object tree MOUNTS IN CHUNKS (40, then
  16 per frame) — a load handed it ~180 rows in one poke, the longest task left (434-565 ms on a
  CPU x6 phone). svelte-check 333 -> 332 (the inline filter callback was a counted error).
- packRefs.warmTemplate compiles BOTH program variants: the canvas one and the render-target one
  the post-processing composer draws with (three keys a program by tone mapping + output colour
  space) — the composer's programs were linking inside the first frame (260 ms).
- sessions/autosave: the scene's environment is restored BEFORE the objects, so pieces warming
  as their packs land compile for this scene's lights, and the right sky shows at once.
- Cancel stays offered while kit models are still arriving; the bar reads "n / N objects" in
  both phases.
- scene-load suite: a fresh page per measured load (parsed pack files are cached in memory, so
  a same-page re-open was instant), pack files routed with a staggered 0.6-2.7 s delay,
  store-sampled count-up, Cancel hit-tested, the menu timed against an idle reference.
- scene-load-trace: PACK_DELAY=1 for the same delay.

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

- lod-real-packs (new, skips without LOD_PACKS_DIR): 33-pack-fix-lod's real level files
  (architecture-kit Gate + WallStone, default Duck with its camera node + parent scale,
  cube_diorama Cat with 3 meshes) each build as name-matched SWAPS covering every mesh and
  draw fewer triangles far away (Gate 5440 -> 1080, Duck 8424 -> 1684, Cat 20108 -> 4014)
- lod-panel 7.3 was a check that could not fail (it compared a count with itself): it now
  counts lod messages before/after the overlay toggles
- lodGroupStats levels carry `paired` + `error`
- held battery on this head: lod-group 24/24, lod-panel 49/49, vr-props-panel, lod (31),
  inspector, undo, lod-real-packs all ALL PASS; vitest 400; svelte-check 333/47 = baseline;
  npm run build green (dev server stopped)
- counterfactuals: hysteresis (vitest: a 100-frame bob on an edge switches once; band 0 pops
  back at once); a block write taking effect the same frame (lod-group 4.1 was red before
  24fe36b's same-frame re-read); shared materials (1.8: the mesh wears its own material
  mid-draw; 5.2 shows the override path is a copy)

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

- The report: "Stars room is too heavy for mobile users, it lags". Measured on a phone
  profile (412x915 @2.625, isMobile + hasTouch, CPU x6, scripts/perf-games.cjs --phone):
  the governor sat at level 0 — a 100-call game is LIGHT by the desktop budgets and a light
  scene is never governed — so a phone drew 32 render passes a frame at 2.6 Mpx each
  (shadows, the authored AO, bloom, SMAA).
- qualityGovernorCore: PHONE_START_LEVEL = 4 (shadows off, 72 % resolution, AO off; the
  look stays) + pure isPhoneLike (coarse pointer, no hover, short side <= 820 CSS px).
  qualityGovernor: decided once at load; a phone starts there in auto mode (no floor - a
  phone that holds its frames walks back up) and context() judges it heavy, the headset's
  rule, so slow frames step it further. Auto off and a game's Quality pin still win.
- flowRuntime handleStamp/triggerStampFor read the existing edgesInto index instead of
  scanning every edge per call (24 latches x 3 handles x ~700 edges a tick); physics
  collectParams indexes nodes once (it was O(edges x nodes) on every flowGraphs change
  mid-sim). Both were top app frames in the Stars Room profile; gone after.
- Stars Room, phone profile before -> after: calls 101 -> 47, render passes 32.2 -> 15.3
  a frame, drawing buffer 2.60 -> 0.65 Mpx, p50 50 -> 33 ms, p99 200 -> 83 ms
  (lanes-30/after-33/33-games/perf-phone-{before,after}.md; Towers 83 -> 48 calls,
  p50 66.7 -> 33.3). A desktop GPU stands in for a phone's: a real phone reading is OWED.
- scripts/perf-games.cjs --phone (+ renders/frame, Mpx (dpr), physical/transparent columns).
- NEW suite quality-phone 10/0 (phone vs desktop page, the real Stars Room); CF (phone
  detection off) 5 red. vitest +3 (381). game-stars-room 72/0, logic-nodes 77/0.
  flow-physics-actions 8.5 is red IDENTICALLY on HEAD (A/B, both 35/2) - pre-existing.
  svelte-check 333/47.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	src/lib/qualityGovernor.js
#	src/lib/qualityGovernorCore.js
…d frame hold

- packRefs.warmPrograms(root): one compile per mesh per slice, canvas AND render-target variants,
  then each program's FIRST USE (getUniforms) one per slice — measured: the first frames created
  no program the warm-up had not, the cost was three's first-use link WAIT (no parallel-compile
  extension) plus the environment change re-keying every existing material. warmTemplate uses it.
- sceneLoader.holdFrames()/releaseFrames()/framesHeld() (bounded by HOLD_MAX_MS = 2 s):
  Outline.svelte skips its render while a scene load re-links programs and builds — the canvas
  keeps its last frame, the DOM (bar, menus) carries on. sessions + autosave hold from the
  environment restore until the build is in and warmed.
- SceneLoadBar enters with a CSS animation, not svelte's fly (getComputedStyle on mount forced a
  whole-document layout inside the load's longest task).
- Suite (CPU x6 phone, slow pack links): open max 220 ms (from 525), restore max 220 ms (from
  89 468); menu opens in 102 ms mid-load vs 24 ms idle.

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

- sessions/autosave: environment AND post stack restored before the objects, then nextFrames(2):
  the composite shader and the helpers re-keyed by the new fog/lights compile on a frame with
  almost nothing to draw, not on the first frame of the full level. Then the hold + sliced warm.
- sceneLoader.nextFrames(n) (rAF, 100 ms fallback where rAF does not run).
- Suite: restore max 166 ms (PASS, from 89 468); open max 204 ms (from 525).

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

- FpsCounter: the detail parts are joined by " · " (svelte trimmed the whitespace:
  "251.2 ms·14 calls")
- editor-ui-33: E2 measures the ring where it is DRAWN (an outer box-shadow over the
  neighbour; layers parsed by colour + size); E3 corner pixels >= 1 px outside the FAB; Q1
  tier reads set+read in one page task on microtasks (the live meter republishes per rAF)
- edit-move-games: an object a flow/module effect animates (Waves' Goal core) is judged by its
  BASE pose (suspendAnimation/read/resumeAnimation), and a fresh-load failure names dx/dy

Counterfactuals (lanes-30/after-33/33-editor-ui/counterfactual*): with `p:hover` restored the
well corners stay pill-grey beside a hovered Interact (E3.1/E3.2 RED); with classActive back on
the toggle a rgb(255,213,204) 4px ring paints the neighbour (E2.1/E2.2 RED). The park: the
game-running probe before c561f24 measured Towers 10/44, Stars Room 26/49, football 1/60 moved
objects not staying (run1/run3.log).

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

- sessions/autosave: environment -> two frames -> post stack -> two frames: each re-keys
  programs on its own near-empty frame (measured ~60 + ~40 ms of links together).
- qualityGovernor ignores frames while a scene load runs (desktop and XR): it answered a
  load's slow frames by dropping the pixel ratio, and that setSize was the longest task of a
  phone Restore (432 ms at CPU x6, tavern).
- 'Session loaded' waits for the kit models (and says nothing for a cancelled/superseded
  load): its fly entrance forced a whole-document layout mid-load.
- CLAUDE.md: the sceneLoader architecture entry.
- Suite (CPU x6 phone, slow links): open max 197 ms, restore max 148 ms, menu 66 ms mid-load
  (26 idle) — 28/28.

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

- physics: with nothing dynamic, a sim start simulated the (sticky) selectedObject — the
  door just placed. It fell over and every Interact click on it became a carry (the
  pack-behavior 7.x flake). Functional items (userData.behavior) are skipped; a door-only
  scene now gets the existing "Nothing to simulate" toast.
  Counterfactual: pack-behavior 7.0a (the door dynamic) and 7.3/7.7 go red without it.
- animatedImports: with registration no longer autoplaying, nothing scheduled the action
  when `playing` later turned true (a press, or a restored playing save) — the state said
  playing and nothing moved. setAnimationState schedules it on the first play.
  Counterfactual: animated-models "clip animates on A/B", animation-persist "animates
  before saving / after the round trip" go red without it.
- packBehaviorDebug grows click diagnostics (clicks, lastClick {mesh, result}).
- suites: pack-behavior primes the walker, adds a real dynamic crate + 7.0a/7.0b, samples
  the preview window (10.1); animation-persist's panel section starts from a playing clip.

Battery (vite inside e2e-slot, PEER_CONFIG): pack-behavior 51/0, animated-models 15/0,
animation-persist 23/0, game-audio 26/0, packs-explorer 10/0. svelte-check 333/47 (= base),
vitest 396/396, check-storage clean, npm run build green.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…all-panels sharpness probe (33 X1)

- 1.18 (merged) keeps the headset eye buffer full (b8a785f, vr-panel-sharpness 6/0 here).
  Extending the probe to EVERY canvas panel: text in a canvas is only as sharp as the
  canvas, so a texture stretched over more headset pixels than it has texels reads soft on
  a sharp eye buffer. Measured as texels per headset pixel (texelRatio, a Quest 3's ~25
  px/deg at the panel's real distance): board 0.78x and wrist card 0.60x were magnified;
  strip 1.07x and banner 1.13x already fine.
- vrGamePanel: SCALE 1.5 -> 2 (board + pause menu 1.06x at 1.2 m) and WRIST_RES 2 (the
  320x300 wrist layout drawn at 640x600 through drawCard's new `res`, hits returned in
  canvas px; 1.31x at 0.44 m). Layouts unchanged.
- NEW suite vr-panel-sharpness-all 10/0: board, pause menu, wrist, strip, banner at their
  real poses + every visible vr-* canvas mesh is in the probe + a rendered check (the wrist's
  text edge energy from the eye, full vs half its texels: x1.40). CF (SCALE 1.5, WRIST_RES 1)
  -> 3 red (0.80x / 0.65x / 0.80x). troika-Text panels (radial, objects, props, keyboard)
  are SDF and resolution independent.
- vr-game-panel 35, vr-laser-panels 25, vr-two-hand-grab 19, game-towers 21,
  game-shell-games 71, game-menu-layout 36 (re-run at the new scale) all green.
  svelte-check 333/47.

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

- sessions/autosave: environment -> hold + sliced warmPrograms of the (empty) scene -> post
  stack -> release + two frames for the composer's own shaders (compile() cannot reach them)
  -> hold for the build. The env re-key no longer links inside a frame.
- packRefs: the checksum cache keyed by buffer versions is GONE — pack-refs caught an
  in-place vertex move without needsUpdate reading as pristine, which would save an edited
  piece as a stub (the edit lost on reload). Counterfactual = that pack-refs check, red with
  the cache, green without.

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

templates-modal (software GL) went red after the whole-scene warm became awaited before the
build: a software link is hundreds of ms per program, so the scene stayed empty past the
check's wait. The warm carries on in the background; the load never waits on the driver.

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

- Outline.svelte registers warmComposerPasses through sceneLoader.registerComposerWarm: every
  pass (and each effect's internal passes) compiled against the target it draws into, one per
  slice, then first use per slice. The load paths call it (bounded by within) while the
  viewport is held, after restoring the post stack.
- scene-load section 7 builds 1200 ordinary meshes (300 measured 166 ms even unsliced — too
  small to prove the slicing guard).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ompiles in batches of twins

- sceneLoader.pump keeps the SHARED slice clock: a loop awaiting one scheduled item at a time
  started a fresh pump per item through microtasks, and the whole loop ran as ONE task —
  measured 881 ms of program warm-up on a 1200-mesh scene, and the source of several earlier
  residual tasks inside pump.
- packRefs.warmPrograms: compile(object, camera, scene) walks the whole scene for lights per
  call, so per-mesh compiles were quadratic. Plain meshes compile in batches of 48 throwaway
  twins sharing their geometry + material (which prepares the real material); skinned /
  instanced / other drawables compile as themselves.
- scene-load-trace: SLUGS=many-boxes (MANY=1200) — a synthetic scene of ordinary meshes.

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

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AlexZ005 and others added 25 commits October 2, 2026 03:56
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…iece in one draw call

The 1.18 levels drew 320-394 calls per frame in the headset analogue (Tavern 394 at the
spawn, Castle 371) against the Quest budget of 150, while their 224/210 meshes come from
only 45/44 distinct pack meshes. A kit level IS a few pieces repeated.

- kitInstancing.js: a render-time pass (lod.js registerBatchPass, AFTER the level swaps)
  groups eligible meshes by (the geometry they draw, the template material, a 16 m column
  centred on the origin, the shadow flags), hides the members for that render call and
  draws one scene-root InstancedMesh per group of two or more. The tree, picking, physics,
  the mesh tools and every serializer never see it (the lod.js rule).
- Eligible = a refilled pack piece still pristine where it shows: its own geometry
  (unedited), its own material, not transparent, material signature equal to its
  template's. Selected pieces, pieces another pass hid or re-materialled, hidden roots
  are drawn as themselves.
- packRefs.instanceOf records which template mesh each copy was cloned from
  (kitMeshSource, a WeakMap).
- kitInstancingCore.js (pure): the column key, the material signature, the rules.
- Settings > Performance "Draw repeated kit pieces together" (LOCAL, default on).
- scripts/perf-levels.cjs + level-views.cjs: the draw-call probe for the General-tab
  levels from named viewpoints (desktop look, and the headset analogue: post off,
  shadows off as the XR governor's entry floor does).

Measured (perf-levels, headset analogue, 1.18 levels authored on this union):
Tavern spawn 394 -> 70, Castle spawn 371 -> 75, worst view 83.

Tests: vitest kitInstancingCore 12; e2e kit-instancing ALL PASS 14 (fewer calls, same
picture, picking, stubs saved, selected/recoloured/hidden/moved/cleared).
Counterfactuals: signature check removed -> recoloured wall draws template colour (2 red);
root-visibility check removed -> hidden pieces still drawn (premise + hidden red).
svelte-check 332/47 (= union), vitest 430, check-storage clean.

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

A door from the interactive kit is an animated import (its mixer binds the parsed tree, so
it cannot be packRefs' hollow stub), and a save carried its whole GLB: 0.5-1.4 MB of base64
per door in every .tpscene and every autosave. Measured: two DoorWoods = 1,320,948 B.

- animatedImports: an animated import that IS a pack file saves `animRef = {pack, item,
  path}` (PACKS_BASE-relative, the kit-stub rule) instead of `bytes`; the restore fetches
  the file once per url and parses it as before; the objectfile message, the undo entry and
  applyObjectFile carry the reference so a peer's or an undone copy re-saves small too. An
  entry with bytes restores and re-saves exactly as before; the wire still sends bytes.
- fileHandler.importFile stamps `userData.animRef` from the drop's packRef for an animated
  file (it used to drop the reference). The Explorer drop already passes it.
- author-templates: a `kit` object whose pack row carries a `behavior` (contract P2) is
  placed through importFile - the Explorer drop's own call - with the row's behavior and
  LOD group, rotation and scale applied after.

Tests: e2e anim-pack-refs ALL PASS 11 (two doors = 1,204 B, PACKS_BASE-relative, behavior
+ LOD kept, restored from the pack, ONE fetch for two copies, re-save stays a reference,
an entry with bytes restores as before). Counterfactual: the fileHandler stamp removed ->
6 red (1,320,948 B saved). svelte-check 332/47.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A batch is culled as one object, so an off-screen member was drawn for every on-screen
member of its batch. The pass now leaves every member outside the camera frustum out of
its batch (three culls it as an ordinary mesh, no call), which keeps triangles where the
frustum puts them - and makes wide columns free, so CELL_METRES goes 32 -> 128.

Measured (perf-levels, headset analogue, the 33 levels' worst views): Tavern 460k -> 327k
triangles, Wizard's Tower 375k -> 272k, Market 302k -> 237k; calls 120 -> 112 (Tavern back
corner) with the wider columns. perf-levels gains --cell to try column sizes.

Tests: kit-instancing +1 check (members 12 in the full view -> 5 looking at the west end).
Counterfactual: the frustum test removed -> that check red (12 -> 12).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e top of its frame

Two pre-existing collider faults the new scenes' doors walked into:

- charController: rapier's KinematicCharacterController swept with NO query filter, so
  every sensor - the grass, flowers, rugs and water the 30c levels mark pass-through, a
  trim, a trigger volume - was a wall in Play. Found at the Tavern's open front door: a
  pass-through wainscot trim held the walker at the threshold (z 4.22). Both sweeps now pass
  QueryFilterFlags.EXCLUDE_SENSORS.
- behaviorCore (33 P2 frame colliders): a LOW leaf in a TALL frame (the town kit's garden
  gate, ~1 m of picket under a 1.96 m frame) left the frame box above the leaf as one solid
  slab across the gateway, so the open gate still stopped the walker. A vertical passage now
  runs to the top of its frame box when more than 0.4 m of frame stands over the leaf (a
  door fills its frame and keeps its lintel); and a leftover thinner than 2 cm (the gate's
  static iron straps, 1 cm proud of the leaf) is dropped instead of becoming a 1.76 m wide
  invisible wall.

Tests: e2e walker-sensors (walks through a sensor wall, stopped by a solid one);
vitest behaviorCore +2 (the measured FenceGate boxes; the straps).
Counterfactuals: the filter flag removed -> walker-sensors red (stopped at z -2.53);
each behaviorCore rule removed -> its test red.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…treet; Wizard's Tower and Market Town Square

scripts/level-templates.cjs (the General-tab levels, authored by author-templates):

- TAVERN INTERIOR (the architecture shell, updated): the front door and the kitchen door
  OPEN (interactive-kit DoorWood / DoorStudded, frames included), the street window's
  shutters close, the loft chest and drawers open; furnished from the Interiors kit (bar
  counters + back-bars under the balcony, a stone fireplace, a tavern table set, a dining
  set, a chandelier on a beam, a kitchen run, a double bed and a wardrobe, teal wainscot);
  a slate gable roof (the hall is open to it); outside a Town & Market street (sidewalks,
  curbs, a crossing, lamp posts, a bench, barrels, a cart) and two cottages across it. You
  arrive on the road facing the front door. 32 KB.
- WIZARD'S TOWER (new): a three-storey 8 x 8 m stone tower at dusk - an alchemy lab
  (trapdoor, chest, lever), a study (rune rug, lectern, desk, armillary, drawers, shutters),
  a bedchamber (wardrobe that opens) and a roof terrace with a telescope; a garden gate and
  the tower door open; an ambient banner stirs above the door. Hero props: arcane-kit
  (packs feat/33-arcane-kit). 24 KB.
- MARKET TOWN SQUARE (new): a 16 x 16 m paved square - fountain, three stalls with goods,
  a well, benches, lamps, banner poles, a clock tower, a road with a cart, cottages round
  it - and a bakery you walk into (its door and shutters open). 28 KB.
- a shared cottage() and street() helper; every building on the kit grid.

Quest budget (draw calls per frame, headset analogue, every named view in
scripts/level-views.cjs): Tavern worst 135, Tower 136, Market 98 (was 394 for the 1.18 Tavern).

Tests: e2e level-templates-33 ALL PASS 67 - General-tab card load, every kit piece refilled
and every functional piece registered, nothing moves in Edit, a REAL click opens and shuts a
door in Interact (leaf 0 -> 95 deg), the ambient banner runs in Interact, Play spawn + sim +
a readable frame, each door STOPS the walker shut and lets it THROUGH open, stairs to every
storey, <= 150 calls from every view. Its earlier red runs found the sensor and gate faults
fixed in the previous commit and the Market's missing dynamic bodies (no sim on Play).
levelTemplates unit: five levels, all on the kit grid, every item in the union packs.
level-templates (30c) keeps the Castle and the Forest.

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

The user: "When opening another scene it should ask whether to keep all modules
... in settings it can be adjusted (automatically unload, automatically keep,
the default is to ask) ... the user should know about the behavior", and
"opened waves, then towers - music from waves stays and the gun".

- sceneSwitch.js: the setting (Settings > Scene > "When opening another scene":
  Ask / Keep modules / Unload modules, safeStorage scenes:modulesOnOpen) and
  prepareSceneSwitch(payload). The ask lists the loaded USER modules the scene
  being LEFT uses and the incoming one does not need (a tool no scene uses and
  every core module are never asked about); Unload primary, Keep, "Remember my
  choice", Cancel aborts the open. The unload runs when the load really applies
  (solo now, a peer proposal on acceptance). Unload = the live teardown journal +
  switched off in Modules (persisted) + a toast with [Modules].
- The "This scene uses modules" prompt's Enable is LIVE now (it said "reload").
- Explorer Open: travelToLevel {askModules, freshGame} - the ask, and the file's
  OWN game state instead of fork 3's carry (kept for the travel node / Go-to):
  a Jam Room round and its variables no longer carry into Towers.
- applySession: `quiet` opt; sceneArrived settles the scope at the end.
- confirmDialog.showChoiceEx (items + one checkbox + checkedLabel).
- boot startSceneSwitch; debug hook +sceneSwitch +sceneScope (221/221/221).

e2e (real .tpscene + module zips, Games-tab path): scene-switch ALL PASS
(Waves -> Towers ask -> Unload: music, gun root, frame tasks 22 -> 8, objects
gone, Towers' 12 levels), scene-switch-keep ALL PASS (Keep + Remember, left
behind, no ask with the setting, the setting's Unload toast, live Enable),
scene-switch-games ALL PASS (Untangle -> Stars by the setting, Football ->
Jam Room lists Football only, Explorer Open starts fresh while a travel hop
still carries). Counterfactuals: ownerInScope -> true turns keep 1.13 red (the
Waves spawn places the Towers player); 3.6 (the travel node carries) is 3.4's
counterfactual in-test. vitest 391/391, svelte-check 333/47, build green.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The user: "after hitting clear scene in an opened game example I still see the
Menu button and the ability to play ... clear scene does not really clear. The
modal should say clear only objects, or modules and everything else ... pick the
best decision here".

- confirmClearScene is ONE modal (#confirm-clear-scene): "Clear objects" stays the
  primary (today's behaviour) and ONE box, "Also reset the game setup and unload
  its modules", whose hint names what (game menu & HUD, play & physics settings,
  music, sky & look, flow nodes; the modules) and which relabels the button
  "Clear everything". OFF by default, ON when there are no objects. A checkbox,
  not an Advanced chevron: it is the only extra choice there is.
- Clear everything = sceneSwitch.clearSceneEverything: unload the scene's
  modules + apply an EMPTY scene payload (replicated, quiet) - no game chip,
  no pause menu, no flow, no HUD, the play block, game state and sky reset.
- Objects only: a toast "Still here: ..." with [Clear those too].
- The Templates "Blank scene" card = a new scene: "Start a blank scene?" and
  always the full reset (#confirm-blank-scene).
- The Sidebar row has an id (#clear-scene) and closes the menu behind the modal.
- The dialog answers the click at once: sceneSwitch is a STATIC import here and
  the setup check reads primed refs. Through dynamic imports the modal took ~9
  frames (measured 4.8 s in-page on Waves in the slow renderer, 9-15 s by click);
  now ~1 ms.

e2e scene-switch-clear ALL PASS (modal < 8 s by click on a 2 fps page; objects-only
leaves the setup + says so; everything resets it and unloads; Blank). templates-
modal ALL PASS (its Blank section moved to the new modal). Counterfactual: the
dynamic-import version reads 9303/8025 ms -> red on the prompt check. (Reverting
the Towers clear fix alone stays green here: the scene USES towers, so the scope
also hides its levels - guarded twice.)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The sceneScope / sceneSwitch architecture entry, the Clear scene modal, the
suites and the 35-50 s per-open timing finding; api.inScene in the SDK section.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s that open, no autoplay)

# Conflicts:
#	PACKS.md
#	src/App.svelte
#	src/components/editors/Explorer.svelte
#	src/lib/explorerDrop.js
#	src/lib/fileHandler.svelte.js
#	src/lib/wireValidate.js
…dow, with visible progress

# Conflicts:
#	src/App.svelte
#	src/lib/packRefs.js
…scene, game B never inherits game A

# Conflicts:
#	src/App.svelte
#	src/lib/sessions.js
…, phones start lighter, crisp VR panels

# Conflicts:
#	src/lib/vrGamePanel.js
…aimInput('sticks'); every level + tabs in the VR pause menu
… Tavern rebuilt; kit instancing

(the branch's accidental .lane-ready scratch file is dropped in this merge)
…lt from the kits

The user asked to "update architecture shell scene"; 33-scenes rebuilt the Tavern (the
planner's reading), and the card actually titled "Architecture shell" stayed a grey primitive
room. It is now a kit room (level-templates SHELL_DEF): sandstone walls on flagstones, the
Interactive Kit's studded door in its frame (opens in Interact/Play), a shuttered and a
glazed window, columns, oak beams, a slate half roof, a spawn outside the door and a few
dynamic crates so Play starts a world.

- author-templates: a `remote: false` def field (seed-only); the greybox becomes
  `architecture-shell-greybox`, the offline seed's copy, never written to --out.
- levelTemplates unit test: six General-tab levels; the shell is one room (> 60 pieces).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…clock; docs + baseline

- scene-switch-clear 4.5 waits for "not a game any more": since 33-scene-load the apply is
  SLICED (objects empty before the HUD + play block are replaced), so the first read after the
  objects vanish saw the old scene (red on the union, green with the wait; 35/0).
- animated-models "peers in phase" compares the peers' CLIP CLOCKS (tick + runtimeNow + wall time
  in one task per page) instead of two poses read in two evaluates — red 3/3 on a loaded box,
  the 33-anim-core branch included (A/B), while the pose is a pure function of the synced clock.
- CLAUDE.md: round-33 entries (LOD groups, functional pack items + animRef, kit instancing,
  editor-ui/games/sticks) and five gotchas.
- check-baseline.json ratcheted 333 -> 332 / 47 (the union's only svelte-check delta vs main is
  one REMOVED error).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- scene-load §6 clicks EXACTLY "Restore": on the phone profile 33-games' quality toast also
  offers "Restore full quality", and `hasText: 'Restore'` picked it first, so the session was
  never restored (6.1-6.5 red on the union, green with the exact match; the exclusive run is
  ALL PASS, longest task 151 ms — the 237/277 ms reads were the other slot's load).
- animated-models launches on the GPU backend: three pages (the late joiner C) on software GL
  is the documented 1.17 three-page gotcha (C's Connect input never appeared, 2/2) — 14/0.

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

33-games starts every phone at a lighter quality level, so this toast (26-D) now shows on
EVERY phone load — beside the session's own "Restore" toast after a reload, which is exactly
the user's L1 path (phone, reload, Restore). Two buttons starting with "Restore" side by side:
the e2e suite picked the wrong one, and so would a thumb, undoing the phone fix without
restoring anything.

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

Both measured inside the Quest budget in the headset analogue (forest 77 / 31 calls, shell 32 / 31).

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

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@AlexZ005
AlexZ005 merged commit e16fbb9 into main Oct 2, 2026
4 checks passed
@AlexZ005
AlexZ005 deleted the feat/1.19 branch October 2, 2026 11:15
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