← back to Quadrille Showroom
Age View Phase-5.5 (contrarian pass): coalesce avatar rebuild, tighten in-page gate, capture screenshots
bb091f8be641e9105e24f73cbb30c80b2fddfb2a · 2026-07-01 10:01:10 -0700 · Steve
Contrarian final gate found three real holes; fixed the two code ones:
1. setAvatarAge() rebuilt the whole primitive avatar on EVERY slider input tick →
dozens of full teardown/rebuilds per second mid-drag (the real FPS-gate failure
mechanism). Now coalesced to ≤1 rebuild/animation-frame via coalesceAvatarAge();
buildAvatar() stays synchronous + byte-for-byte (boot + OFF-reset call it directly).
2. In-page seniorGate() used coarse type-size steps and never checked HEAD size — a
softer bar than the spec, so its 'all-pass' would've been green even before the
band type-size fix. Retightened to the interpolated 65→85 bar incl. heads, matching
verify-ageview-gate.mjs. Still all-pass (the bands genuinely meet the stricter bar).
3. Cross-engine harness now captures a screenshot per engine/age (was counters-only).
Re-ran both verifiers post-edit (validation-loop): static gate ALL 10 PASS; cross-engine
PASS on Chromium/Firefox/WebKit, 0 console errors, tightened seniorGate all-pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
M public/js/ageview.jsM public/js/showroom.jsM scripts/verify-ageview-crossengine.mjs
Diff
commit bb091f8be641e9105e24f73cbb30c80b2fddfb2a
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jul 1 10:01:10 2026 -0700
Age View Phase-5.5 (contrarian pass): coalesce avatar rebuild, tighten in-page gate, capture screenshots
Contrarian final gate found three real holes; fixed the two code ones:
1. setAvatarAge() rebuilt the whole primitive avatar on EVERY slider input tick →
dozens of full teardown/rebuilds per second mid-drag (the real FPS-gate failure
mechanism). Now coalesced to ≤1 rebuild/animation-frame via coalesceAvatarAge();
buildAvatar() stays synchronous + byte-for-byte (boot + OFF-reset call it directly).
2. In-page seniorGate() used coarse type-size steps and never checked HEAD size — a
softer bar than the spec, so its 'all-pass' would've been green even before the
band type-size fix. Retightened to the interpolated 65→85 bar incl. heads, matching
verify-ageview-gate.mjs. Still all-pass (the bands genuinely meet the stricter bar).
3. Cross-engine harness now captures a screenshot per engine/age (was counters-only).
Re-ran both verifiers post-edit (validation-loop): static gate ALL 10 PASS; cross-engine
PASS on Chromium/Firefox/WebKit, 0 console errors, tightened seniorGate all-pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
public/js/ageview.js | 11 ++++++++++-
public/js/showroom.js | 17 ++++++++++++++++-
scripts/verify-ageview-crossengine.mjs | 4 +++-
3 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/public/js/ageview.js b/public/js/ageview.js
index d590333..5c31394 100644
--- a/public/js/ageview.js
+++ b/public/js/ageview.js
@@ -312,18 +312,27 @@ function setOn(v) {
// OPAQUE band hex pairs + reports type/target sizes per the spec thresholds. ----
function seniorGate() {
const out = {};
+ // Type-size requirement is INTERPOLATED across the spec anchors 65→85 (body 20→28,
+ // head 30→44) and the check includes HEAD — matching scripts/verify-ageview-gate.mjs.
+ // (Prior version used coarse steps + skipped heads = a softer bar than the spec;
+ // tightened 2026-07-01 after the contrarian pass.)
+ const reqBody = a => Math.ceil(20 + (Math.min(a, 85) - 65) / 20 * 8);
+ const reqHead = a => Math.ceil(30 + (Math.min(a, 85) - 65) / 20 * 14);
+ const reqBtn = a => (a >= 85 ? 56 : 48);
[65, 75, 85].forEach(a => {
const b = bandFor(a);
const bodyLc = apcaLc(b.fg, b.panel);
const ctaLc = apcaLc(b.ctaFg, b.cta);
+ const rb = reqBody(a), rh = reqHead(a), rk = reqBtn(a);
out[a] = {
band: b.id, opaque: !!b.opaque,
bgPair: { panel: b.panel, fg: b.fg }, ctaPair: { cta: b.cta, ctaFg: b.ctaFg },
bodyLc, ctaLc,
bodyAbs: Math.abs(bodyLc), ctaAbs: Math.abs(ctaLc),
type: b.body, head: b.head, target: b.btn,
+ reqBody: rb, reqHead: rh, reqBtn: rk,
pass: Math.abs(bodyLc) >= 60 && Math.abs(ctaLc) >= 75 && b.opaque &&
- b.body >= (a >= 85 ? 28 : 20) && b.btn >= (a >= 85 ? 56 : 48)
+ b.body >= rb && b.head >= rh && b.btn >= rk
};
});
// The slider control itself at age 85 (contrarian #2): persistent on-screen, so it's
diff --git a/public/js/showroom.js b/public/js/showroom.js
index d97c775..1cb62de 100644
--- a/public/js/showroom.js
+++ b/public/js/showroom.js
@@ -1119,6 +1119,21 @@ function ageRigParams(age) {
let avatarAge = 28; // current avatar age; 28 == today's rig
let avatarUpperRig = null; // named sub-group (torso+neck+shoulders+arms) — the stoop pivot
+// Age View drags the slider continuously (one 'input' per pixel), and setAvatarAge does a
+// FULL primitive teardown+rebuild — unthrottled that's dozens of rebuilds/sec + GC churn
+// mid-drag (the real FPS-gate failure mode; contrarian 2026-07-01). Coalesce rapid
+// setAvatarAge calls to at most ONE rebuild per animation frame. buildAvatar() itself stays
+// synchronous + byte-for-byte identical (boot + OFF-reset paths call it directly).
+let _avatarAgePending = null, _avatarAgeRaf = 0;
+function coalesceAvatarAge(age) {
+ _avatarAgePending = Math.max(12, Math.min(85, age));
+ if (_avatarAgeRaf) return;
+ _avatarAgeRaf = requestAnimationFrame(() => {
+ _avatarAgeRaf = 0;
+ if (_avatarAgePending != null) { const a = _avatarAgePending; _avatarAgePending = null; buildAvatar(a); }
+ });
+}
+
function buildAvatar(age) {
// Clean rebuild safety (rebuilds shouldn't stack bodies)
if (avatarRig) { scene.remove(avatarRig); avatarRig = null; }
@@ -5145,7 +5160,7 @@ window._qh = {
// setEnvForAge does CHEAP scalar/colour env tweaks only (exposure + ambient/hemi/key
// intensity + tint) — no geometry/layout change, so FPS is untouched (contrarian #5).
// resetEnv restores the baseline gallery light.
- setAvatarAge(age) { buildAvatar(age); },
+ setAvatarAge(age) { coalesceAvatarAge(age); },
get avatarAge() { return avatarAge; },
ageRigParams,
upperRigState() {
diff --git a/scripts/verify-ageview-crossengine.mjs b/scripts/verify-ageview-crossengine.mjs
index a3b8fd7..be1ec42 100644
--- a/scripts/verify-ageview-crossengine.mjs
+++ b/scripts/verify-ageview-crossengine.mjs
@@ -36,7 +36,9 @@ for(const [name,launcher] of engines){
wings:(window._wingBoards&&window._wingBoards.length)||0,
bodyClass:document.body.className
}));
- console.log(` age ${String(age).padStart(2)} → readout ${snap.shownAge} · band "${snap.band}" · wings ${snap.wings} · fps ${snap.fps}`);
+ const shot=`recordings/ageview-${name}-age${age}.png`;
+ await page.screenshot({path:shot});
+ console.log(` age ${String(age).padStart(2)} → readout ${snap.shownAge} · band "${snap.band}" · wings ${snap.wings} · fps ${snap.fps} · shot ${shot}`);
if(snap.wings<1){ console.log(` ✗ wing wall empty at age ${age}`); hardFails++; }
}
const gate=await page.evaluate(()=>window._ageview.seniorGate());
← d4295bd Age View Phase-5.3: cross-engine render gate (Chromium/Firef
·
back to Quadrille Showroom
·
5x sweep 1: fix duplicate id=density-wrap (7x → class); docu 604475e →