The retrace after the label-swap fix showed getParameters unchanged
(~2.5s / ~4% throttled main thread) — the real driver is Three r158+'s
transparent-DoubleSide two-pass path: renderBufferDirect renders such
objects back side then front side, setting material.needsUpdate BOTH
times, i.e. a full getParameters/program-cache lookup twice per object
per frame, plus double draw calls. (Found by reading the two-pass
branch in the vendored three.module.min.js right next to the
getParameters call site.)
All 18 transparent DoubleSide materials in this renderer are flat
unlit quads — technique markers, sustain rails, chord frames, lane
planes, halo bars — where the two-pass self-occlusion ordering buys
nothing. Declare forceSinglePass: true on all of them.
Also corrects the _setLabelMap comment's churn attribution (that fix
removes the label-swap contribution; this one removes the dominant
source). Plugin 3.31.1 -> 3.31.2.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A 4x-CPU-throttled retrace (the honest weak-hardware proxy) surfaced
three residual per-frame costs; stack attribution pinned each:
- getParameters/getProgramCacheKey (~4% of main thread): every pooled
label sprite map swap set material.needsUpdate, bumping
material.version and forcing full program re-resolution next render.
Swapping between two non-null cached textures never changes the
compiled program (USE_MAP define unchanged) — new _setLabelMap()
helper only flags needsUpdate on a null<->texture transition, used at
all 7 swap sites.
- getBoundingClientRect (~1.2%): the 3D highway's per-frame canvas-size
self-check forced a layout read every frame. The CSS-box drift read
now runs every 10th frame (or when the wrap isn't pinned); the
backing-store comparison stays per-frame with cheap property reads
and forces an immediate box read + applySize when it fires.
- set textContent: the core 60 Hz HUD clock rewrote hud-time (and
getElementById'd it) every tick for a display that changes 1/s — now
write-on-change with a cached element ref.
(The remaining textContent writer in the trace is notedetect's
badges.js — external repo, to be filed there.)
tests/js: resize-reframe shape test updated for the hoisted _bsChanged
gate, incl. an assertion that the throttle can never delay the
backing-store path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- isVisible() forces a fresh DOM sample (was serving the rAF loop's
throttled cache, contradicting its 'live DOM check' docstring).
- v3 chrome: reconcile the edge-driven overControls hover flag against
matches(':hover') on the throttled ~6 Hz tick — covers a missed
mouseleave (flag stuck true, transport never hides) and a re-created
#player-controls node with lost listeners.
- highway_3d pre-warm now also covers teachFg/teachSd label textures
and the technique sprite factories (mute X, hammer/pull triangles,
bend chevrons, slide arrows) per active-palette string colour, plus
a maintenance note tying new label styles to the warm list.
- Document that the visibility throttle's manual invalidations are
latency-only (periodic resample self-heals within ~10 frames), and
why highway_3d keeps its local lowerBoundT (downlevel hosts).
External-repo audit (finding 1): staffview, tabview, piano, drums,
keys_highway_3d, drum_highway_3d grepped — no cross-frame bundle
retention or bundle-identity checks found.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Static-analysis follow-ups to the trace-backed fixes; each is cheap
insurance on machines where the profiled headroom doesn't exist.
- highway.js: _makeBundle now mutates one persistent per-instance
object instead of allocating a fresh ~35-field bundle every rAF
frame (xN under splitscreen). Object identity is stable and
meaningless; array fields still swap reference on chart changes,
which field-identity caches rely on. Contract documented in both
CLAUDE.mds.
- highway.js: new bsearchTime (lower-bound on .time) windows the
default 2D renderer's beat-line scan (was O(all beats) per frame);
bundle.lowerBoundT / bundle.lowerBoundTime expose the searches to
custom viz so they stop reimplementing visible-window culling.
- highway_3d: localStorage 'h3d_full_sus' polled at ~1 Hz instead of
every frame (synchronous storage read on the hot path).
- highway_3d: drawLyrics caches the measureText row layout keyed on
(lyrics ref, line index, shown count, font size, width) — per-frame
work is now just drawing over cached widths.
- tests/js: bundle source-shape assertions widened to accept the
assignment form ([:=]) alongside the old object-literal form.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Trace showed frame spikes from Three.js first-use costs mid-song:
shader program compilation (getParameters/getProgramCacheKey) and lazy
texture uploads (texSubImage2D) whenever a chord name, section banner,
or fret label first appeared.
- ren.compile(scene, cam) after initScene (pools already warmed by
feedBack#226, board built, background mounted) so programs compile
during the load spinner.
- Pre-rasterise + GPU-upload (ren.initTexture) the deterministic txtMat
entries: fret numbers 0-24 in the noteFret/fretRow/ghostFret combos
the per-frame paths request.
- Chart-dependent labels (chord template names, section names) prewarm
once on the first draw() after each init, when bundle arrays are
guaranteed populated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Chrome trace showed ~0.5s self-time in _isHighwayVisible (offsetParent
read every rAF frame forces style/layout recalc) and ~1.5s in the v3
player-chrome loop (matches(':hover') per frame, unconditional
textContent/width writes at 6 Hz -> ~1800 layout passes in 63s).
- highway.js: sample offsetParent every 10th frame, cached in between;
fresh sample forced on init/canvas-replace/resize/override-clear.
- player-chrome.js: hover tracked via mouseenter/mouseleave; Up-Next
refs cached, text written only on change (eta coarsened to 1s steps
beyond 10s), progress bar moved from width to scaleX (compositor-only).
- v3.css: bar fill uses transform-origin:left + scaleX transition.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>