feat(scaffold): bake slider thumb emblazon into sphere texture (#278) - #279
Merged
Conversation
Replace the #276 / #277 planar Troika-Text-child billboard with a baked (symbol, color) canvas texture wired into the thumb sphere's material.map. The sphere itself yaw-billboards via thumb.quaternion, so the glyph rides the surface instead of reading as a decal floating in front. New scaffold module bakedSymbolTexture.ts exposes a refcounted (symbol, color) cache — 13 unique pairs across the 17 cluster sliders. Soft release semantics + idempotent Slider.dispose() prevent uncaught throws on lifecycle paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Preview deploy ready — commit https://geometer-pr-279.1bradley-martin1.workers.dev Open this URL in the Quest browser to smoke-test in headset. |
Three.js SphereGeometry's equirectangular UV places (U=0.5, V=0.5) on the local +X surface point, not +Z. faceCamera writes a yaw quaternion that points local +Z at the camera; without compensating for the UV convention, the bare +Z side of the sphere faces the user and the glyph ends up ~90° off to the side. One-line sphereGeom.rotateY(-π/2) in the ctor moves the glyph 90° around the sphere to where faceCamera expects it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pancake smoke surfaced two issues: (1) the texture's UV center landed at sphere-local +X (not +Z), so the glyph faced ~90° off; (2) per-frame yaw-billboarding the sphere read as "still a billboard" — a uniform-colored sphere body has no parallax cue to betray rotation, so the visual was indistinguishable from #277's planar billboard. (1) was patched by sphereGeom.rotateY(-π/2). This commit fixes (2) by dropping the per-frame faceCamera entirely. The thumb gets a static rotation (about local +X by -π/2) so the glyph centers along slider-local +Y, which under the plinth slot's orientation='surface' transform is the drafting-board surface normal in world. The glyph is now truly fixed on the sphere surface — the user reads it from the natural lean-over working pose, and it slides off / foreshortens as they back away. Removes the faceCamera method + 5 scratch fields from Slider.ts and the 4 per-frame dispatches in the cluster scenes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous fix centered the glyph on slider-local +Y, which I'd incorrectly identified as the drafting-board normal. The slab is actually modelled thin in slot-local Z (front face at z=0), so slot-local +Z is perpendicular to the slab face — that's the real outward normal. After the slot rotation (-tilt about +X), +Z maps to world (0, sin(tilt), cos(tilt)) = up and toward user; +Y maps to (0, cos(tilt), -sin(tilt)) = up and away from user, which read in smoke as "eyes rolled back into the head." Drop thumb.rotation.x = -π/2; with identity rotation the glyph stays at mesh-local +Z = slider-local +Z, facing along the slab's actual outward normal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 2 of the binary-search dial on GLYPH_FONT_SIZE_PX per feedback_binary_search_visual_constants. Round 1 (160 px) read in pancake smoke as "wraps a full hemisphere of the sphere" — too distorted by the equirectangular UV curvature at that size. Drop to 112 (0.7 x 160) so the glyph drifts over less of the sphere surface and the curvature reads as a subtle wrap rather than a hemisphere-wide stretch. Bracket tightens to [80, 144]. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 3 of the GLYPH_FONT_SIZE_PX binary-search dial. Round 2
(112) still drifted over more of the sphere than wanted; Brad
asked for 90 ("0.5x original"), so taking 90. Bracket tightens
to [78, 108] as we converge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #278
Summary
Replaces the #276 / #277 planar Troika-
Text-child billboard with a baked(symbol, color)canvas texture wired into the thumb sphere'smaterial.map. The sphere itself yaw-billboards viathumb.quaternion, so the glyph rides the surface instead of reading as a decal floating in front. New scaffold modulebakedSymbolTexture.tsexposes a refcounted(symbol, color)cache — 13 unique pairs across the 17 cluster sliders. Soft release semantics + idempotentSlider.dispose()prevent uncaught throws on lifecycle paths.Plan + review trail
Plan:
_private/plans/278-emblazoned-thumb-texture.md(v3, post second-Sonnet sanity)./roundtable-plan-reviewran across Sonnet (oppositional-reviewer with filesystem access) + GPT-5.5 + DeepSeek V4 Pro. Verdicts: Sonnet REVISE, GPT REVISE, DeepSeek PROCEED.system-uicanvas font (was: hard-coded Apple-first stack with no Quest fallback story); mipmapped minification (was: shimmer hazard in VR);flipYdoc + smoke item; empirical glyph y-offset; backing-store clear at refcount→0; one staleTextimport deletion Sonnet caught.makeSlider()callsites under the v2 factory injection would have polluted cache state across tests. v3 adds_clearCacheForTests()helper wired intoafterEachblocks in bothSlider.test.tsandPointerMigration.test.ts.Test plan
npm test— 573 passing across 42 files (+6 net new behavioral cases: 7 inbakedSymbolTexture.test.ts, 6 new inSlider.test.ts, −7 deleted feat(scaffold): emblazoned-label slider thumbs (solid spheres + per-slider symbol) #276 label cases).npm run buildclean (tsc --noEmit + vite build).a→ labelx², sliderb→y², sliderc→z²) — glyphs read as baked into the sphere surface, not as a decal floating in front.u/v/w→x/y/z) + cross-sections (x₀/y₀/z₀) — subscript₀glyph renders cleanly viasystem-ui.C(YELLOW) appears identically in both Squared and Linear racks; cache hit, not duplicate render.θ/φneutral gray), gradient-levels (θ/φ/kneutral gray), saddle-extrema (x/yVERMILLION/BLUISH_GREEN).x²andx₀where orientation regressions would be most visible).x²/x₀not visibly shifted up/down on the sphere face. TuneGLYPH_VERTICAL_OFFSET_PXper its bracket if off.renderer.info.memory.texturesreturns to pre-cluster-nav value after a full cluster cycle (refcount cleanup).Escapes (recorded in plan §6 if any fire post-smoke)
GLYPH_FONT_SIZE_PXif glyph wraps too tightly on the spheresystem-uifont coverage misses🤖 Generated with Claude Code