?perf instrumentation suite: real GPU timestamps, system tracks, action receipts, DOM panel - #755
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nq2rLE18tFVES2LU6HGokK
…system User Timing tracks - DRAW read info.render.calls (lifetime render() count, never reset) — now drawCalls - GPU time now from WebGPU timestamp queries (trackTimestamp + resolveTimestampsAsync); the old queue-fence delta stays as QUEUE (backpressure), encode CPU as ENCODE - perf-tracks: shared sink emitting DevTools custom tracks (trackGroup Pascal) + per-window counter buckets; perf-observers: longtask observer - spans: frame-cpu (FrameLimiter advance), geometry builders, wall miter/rebuild/CSG, door/window rebuilds, pointer raycast, react-render Profiler boundary - panel: FRAME cpu avg/max, MEM (info.memory + JS heap), visible-only census at 2s, TRACKS readout, clearPerfMeasures per drain; FPS threshold matches the 50fps cap - deleted dormant DebugRenderer (unreferenced; would double-render if mounted) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nq2rLE18tFVES2LU6HGokK
drei <Html> wrappers carry a camera-driven transform, which turns the old overlay's position:fixed into 'fixed relative to the wrapper' — the panel drifted with the camera. PerfMonitor is now a headless in-canvas collector publishing to perf-panel-store; PerfPanel portals to <body>: draggable by header, dockable to the nearest edge as a live fps tab, placement persisted in localStorage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nq2rLE18tFVES2LU6HGokK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nq2rLE18tFVES2LU6HGokK
Edit gestures now produce receipts: begin/commit/cancel bracket a gesture, every perf-tracks sample in between is attributed to it, and the action settles only when dirty nodes + deferred wall rebuilds hit zero and one more GPU sample lands. Receipts surface in the panel (last action + breakdown), the console, and a DevTools 'Actions' lane. Call sites: the interaction scope store as the generic bracket (yields to more specific ones), use-drag-action, 2D floorplan gestures, place/undo/ redo/delete/level-switch; markToolCancelConsumed finalizes cancels. Settle system feeds the ledger per frame at priority 100. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nq2rLE18tFVES2LU6HGokK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nq2rLE18tFVES2LU6HGokK
A node deleted while dirty (undo of a wall split) leaves its mark in dirtyNodes forever — no system clears marks for missing nodes — and that phantom dirt kept every action receipt from settling in furnished scenes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nq2rLE18tFVES2LU6HGokK
…s, no-op undo - the interaction scope now commits only the action IT began (id token), and yields only to an UNCOMMITTED action, so a settling receipt can't swallow the next gesture and a specific call site's cancel is never committed by the generic bracket - devices without timestamp-query settle on the queue fence instead of timing every receipt out - a no-op undo/redo no longer opens a receipt - import order (CI quality) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nq2rLE18tFVES2LU6HGokK
CI typecheck resolves per-package: the perf panel's createPortal import needs react-dom declared, not inherited from hoisting. Same peer + types pattern as packages/editor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nq2rLE18tFVES2LU6HGokK
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e51e2a6. Configure here.
| if (historyCommandDelegate) { | ||
| const result = historyCommandDelegate.redo() | ||
| if (result.kind !== 'empty') markPerfAction('redo') | ||
| return result |
There was a problem hiding this comment.
Delegate undo measured after the jump
Medium Severity
Collaborative undo/redo calls markPerfAction after the delegate has already applied the jump, so the receipt misses the operation itself and only attributes later settle work. The local path marks before temporal.undo()/redo(), so the two history modes report different costs for the same gesture.
Reviewed by Cursor Bugbot for commit e51e2a6. Configure here.
|
|
||
| adoptedRef.current = false | ||
| originalStateRef.current = null | ||
| commitPerfAction() |
There was a problem hiding this comment.
Place action interrupts in-flight gesture
Medium Severity
beginPerfAction('place:item') runs at commit time, not at gesture start. beginPerfAction finalizes any active action as interrupted, so a placing scope opened by setMovingNode (the generic bracket) is cut off at drop. The drag is stored as interrupted and the new receipt only covers the create write.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit e51e2a6. Configure here.
| <> | ||
| {/* DOM overlay, deliberately outside <Canvas> — drei Html wrappers carry | ||
| a camera transform that defeats position:fixed (see perf-panel.tsx). */} | ||
| {(perf || PERF_OVERLAY_ENABLED) && <PerfPanel />} |
There was a problem hiding this comment.
Perf panel hydration mismatches Canvas
Medium Severity
PerfPanel is a DOM sibling of Canvas gated on PERF_OVERLAY_ENABLED, a module const that is false during SSR (window missing) and true on the client when ?perf is set. The server tree has only Canvas; the client tree inserts the panel first, which can fail hydration and remount the WebGPU canvas.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit e51e2a6. Configure here.


Measurement layer for the editor performance pass — everything gated on
?perf, zero cost without it.What's inside
info.render.calls(lifetimerenderer.render()count, never cleared byreset()) — nowdrawCalls. "GPU ms" was a wall-clock fence (onSubmittedWorkDone) bundling CPU encode + queue wait + microtask latency — now real WebGPU timestamp queries (trackTimestamp+resolveTimestampsAsync), with the fence kept as a separate QUEUE (backpressure) line and ENCODE (CPU) split out.<body>(drei<Html>wrappers carry a camera transform that defeatsposition:fixed— the old overlay drifted with the camera). Draggable, dockable to an edge tab, placement persisted.perf-trackssink emits Chrome DevTools custom lanes (trackGroup "Pascal") + per-window buckets for the panel: frame-cpu, geometry builders, wall miter/rebuild/CSG, door/window rebuilds, pointer raycast, react-render, long tasks.window.__pascalPerf.{listNodes,projectNode}for scripted perf runs.DebugRenderer(unreferenced; would double-render if mounted).Diagnosis results and the optimization backlog live in the private repo's
plans/performance/editor-scalable-scene-runtime.md.🤖 Generated with Claude Code
https://claude.ai/code/session_01Nq2rLE18tFVES2LU6HGokK
Note
Low Risk
Instrumentation is gated on
?perfwith no-op fast paths in production; changes are diagnostic UI and timing hooks rather than core edit or auth logic.Overview
Adds a
?perf-gated measurement stack so editor performance work can see real frame cost, per-system work, and end-to-end edit latency without affecting normal sessions.Viewer overlay moves from a drei Html overlay inside the canvas to a DOM panel portaled to
document.body(draggable, dockable, persisted placement). A headless PerfMonitor still runs in the canvas, publishes aggregates via perf-panel-store, and exposeswindow.__pascalPerfprobe hooks for scripted runs. GPU reporting switches to WebGPU timestamp queries when available (trackTimestamp), with encode, gpu-render, and queue fence lines separated; draw stats usedrawCallsinstead of lifetimecalls.perf-tracks centralizes spans (
timeSpan,recordPerfSample) into DevTools “Pascal” lanes and per-window buckets; hot systems (wall miter/rebuild/CSG, geometry, door/window, frame-cpu, pointer, react-render, long tasks) are instrumented.perf-actions brackets editor gestures (drag, place, delete, undo/redo, level-switch) with
begin/commit/cancelandmarkPerfAction, coordinated with interaction scope and 2D floorplan affordances. PerfActionSettleSystem closes receipts only after live dirty nodes clear, deferred wall rebuilds flush, and a post-quiet GPU sample.The editor wraps the main tree in React Profiler when perf is on. react-dom is added as a viewer peer dependency for the portal panel.
Reviewed by Cursor Bugbot for commit e51e2a6. Bugbot is set up for automated code reviews on this repo. Configure here.