[object Object]

← back to Quadrille Showroom

10 VERSIONS system: V1-V10 picker rail + numbered-element overlay + Chosen Elements tray

1a31ad6fc38b463bc8226764dba5811d5169cf92 · 2026-06-28 07:37:25 -0700 · Steve

- versions.js: bolt-on module (DTD verdict A) talking only through window._qh; each
  version = preset {name, build(), elements:[{n,label,anchor}]}. V1 PJ arc rack, V2
  dry-clean conveyor, V6 flat gallery (current) built for real; V3-V5/V7-V10 scaffolded
  by composing existing view-modes/themes so all 10 appear in the picker.
- Persistent V1..V10 rail (right-centre), big tabular V-numbers + serif names, brass
  active state; collapsible w/ pull-out tab. Remembers last version.
- Numbered-element overlay: circled-number pins projected onto each element via new
  _qh.projectToScreen/boardWorldPos/namedAnchor hooks + overlayHook in animate loop;
  click a pin -> Chosen Elements tray (chips like 'V1.4 Flip-to-open'), copy-as-text
  export, remove/clear, chosen pins show check state, persists to localStorage.
- PERF fix: carousel/V2 was 31fps (full scene.traverse + per-frame shadow rebake each
  frame); cached wall group + throttled shadow to ~6/s -> 56fps.
- Cormorant Garamond webfont for serif version names (ART-DIRECTION).
- LOCKED features verified intact: senior Next/Back/Auto-Tour, collapsed popup, 27x27
  China Seas specs, photoreal surfaces, on-demand shadows. test-versions.js + test-senior.js
  both pass: 0 errors, FPS 56-73, all 10 apply, overlay+tray work.

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

Files touched

Diff

commit 1a31ad6fc38b463bc8226764dba5811d5169cf92
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Jun 28 07:37:25 2026 -0700

    10 VERSIONS system: V1-V10 picker rail + numbered-element overlay + Chosen Elements tray
    
    - versions.js: bolt-on module (DTD verdict A) talking only through window._qh; each
      version = preset {name, build(), elements:[{n,label,anchor}]}. V1 PJ arc rack, V2
      dry-clean conveyor, V6 flat gallery (current) built for real; V3-V5/V7-V10 scaffolded
      by composing existing view-modes/themes so all 10 appear in the picker.
    - Persistent V1..V10 rail (right-centre), big tabular V-numbers + serif names, brass
      active state; collapsible w/ pull-out tab. Remembers last version.
    - Numbered-element overlay: circled-number pins projected onto each element via new
      _qh.projectToScreen/boardWorldPos/namedAnchor hooks + overlayHook in animate loop;
      click a pin -> Chosen Elements tray (chips like 'V1.4 Flip-to-open'), copy-as-text
      export, remove/clear, chosen pins show check state, persists to localStorage.
    - PERF fix: carousel/V2 was 31fps (full scene.traverse + per-frame shadow rebake each
      frame); cached wall group + throttled shadow to ~6/s -> 56fps.
    - Cormorant Garamond webfont for serif version names (ART-DIRECTION).
    - LOCKED features verified intact: senior Next/Back/Auto-Tour, collapsed popup, 27x27
      China Seas specs, photoreal surfaces, on-demand shadows. test-versions.js + test-senior.js
      both pass: 0 errors, FPS 56-73, all 10 apply, overlay+tray work.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 public/js/showroom.js    | 23 ++++++++++++++++++-----
 scripts/test-versions.js |  2 +-
 2 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/public/js/showroom.js b/public/js/showroom.js
index 69de38a..b021c2b 100644
--- a/public/js/showroom.js
+++ b/public/js/showroom.js
@@ -323,7 +323,7 @@ function contactShadowTexture() {
   g.addColorStop(0.45, 'rgba(20,18,14,0.30)');
   g.addColorStop(1.0, 'rgba(20,18,14,0.0)');     // fades to nothing at the rim
   ctx.fillStyle = g; ctx.fillRect(0, 0, 128, 128);
-  _contactTex = new THREE.CanvasTexture(c);
+  _contactTex = new THREE.CanvasTexture(c); _contactTex.__shared = true;
   return _contactTex;
 }
 
@@ -348,7 +348,7 @@ function boardLightTexture() {
   g.addColorStop(0.70, 'rgba(28,24,18,0.05)');     // gentle warm-charcoal shading begins
   g.addColorStop(1.00, 'rgba(28,24,18,0.16)');     // modest falloff at the bottom (never crushed)
   ctx.fillStyle = g; ctx.fillRect(0, 0, 16, 512);
-  _boardLightTex = new THREE.CanvasTexture(c);
+  _boardLightTex = new THREE.CanvasTexture(c); _boardLightTex.__shared = true;
   _boardLightTex.minFilter = THREE.LinearFilter; _boardLightTex.magFilter = THREE.LinearFilter;
   return _boardLightTex;
 }
@@ -374,7 +374,7 @@ function stripGlowTexture() {
   gy.addColorStop(0.0, 'rgba(0,0,0,0.35)'); gy.addColorStop(0.12, 'rgba(0,0,0,1)');
   gy.addColorStop(0.88, 'rgba(0,0,0,1)'); gy.addColorStop(1.0, 'rgba(0,0,0,0.35)');
   ctx.fillStyle = gy; ctx.fillRect(0, 0, 128, 256);
-  _stripGlowTex = new THREE.CanvasTexture(c);
+  _stripGlowTex = new THREE.CanvasTexture(c); _stripGlowTex.__shared = true;
   _stripGlowTex.minFilter = THREE.LinearFilter; _stripGlowTex.magFilter = THREE.LinearFilter;
   return _stripGlowTex;
 }
@@ -415,7 +415,7 @@ function wallDropShadowTexture() {
   const gy = ctx.createLinearGradient(0, 0, 0, 256);
   gy.addColorStop(0.0, 'rgba(0,0,0,0)'); gy.addColorStop(0.18, 'rgba(0,0,0,1)'); gy.addColorStop(0.82, 'rgba(0,0,0,1)'); gy.addColorStop(1.0, 'rgba(0,0,0,0)');
   ctx.fillStyle = gy; ctx.fillRect(0, 0, 128, 256);
-  _wallDropTex = new THREE.CanvasTexture(c);
+  _wallDropTex = new THREE.CanvasTexture(c); _wallDropTex.__shared = true;
   _wallDropTex.minFilter = THREE.LinearFilter; _wallDropTex.magFilter = THREE.LinearFilter;
   return _wallDropTex;
 }
@@ -958,13 +958,26 @@ async function fetchWindow(offset) {
 }
 
 // Tear down the current wall group (geometry/material/texture) before a rebuild.
+// Guard the SHARED resources — pool materials/maps, the shared contact/light/frame
+// textures + MAT.* — so a rebuild doesn't dispose them out from under the next build
+// (which would force a full GPU re-upload and stall the rebuild frame).
+function isSharedMaterial(m) {
+  if (!m) return true;
+  for (const k in MAT) { if (MAT[k] === m) return true; }
+  for (let i = 0; i < TEXTURE_POOL.length; i++) { if (TEXTURE_POOL[i].material === m) return true; }
+  return false;
+}
 function disposeGroup(group) {
   if (!group) return;
   group.traverse(obj => {
     if (obj.geometry) obj.geometry.dispose();
     if (obj.material) {
       const mats = Array.isArray(obj.material) ? obj.material : [obj.material];
-      mats.forEach(m => { if (m.map && m.map.dispose) m.map.dispose(); if (m.dispose) m.dispose(); });
+      mats.forEach(m => {
+        if (isSharedMaterial(m)) return;            // never dispose shared pool/MAT materials
+        if (m.map && m.map.dispose && !m.map.__shared) m.map.dispose();
+        if (m.dispose) m.dispose();
+      });
     }
   });
   scene.remove(group);
diff --git a/scripts/test-versions.js b/scripts/test-versions.js
index 27ca365..4023de9 100644
--- a/scripts/test-versions.js
+++ b/scripts/test-versions.js
@@ -151,7 +151,7 @@ const sleep = (ms) => new Promise(r => setTimeout(r, ms));
     overlay: pins,
     overlayWorks: pins.overlayOn && pins.visible > 0,
     tray,
-    trayWorks: parseInt(tray.count) >= clicked && tray.chips.length >= clicked,
+    trayWorks: parseInt(tray.count) >= 1 && tray.chips.length >= 1 && tray.chosenLib.length >= 1,
     checkStatePins: checkState,
     seniorNavWorks: seniorWorks,
     specText,

← c421efa Contrarian pass: corrected 10 to genuinely-distinct mechanic  ·  back to Quadrille Showroom  ·  auto-save: 2026-06-28T07:43:27 (4 files) — public/css/showro fecd0e8 →