Files

Sanctification Card Lab

Bounded Three.js proof for the card-rendering harness plan.

The approved visual baseline is recorded in CURRENT-DEFAULTS.md.

Included proof scope

  • Searchable accepted-card library sourced directly from artifacts/cards
  • Low, Med, and High resolution plus Normal, Borderless, Textless, and Boundless printing controls
  • Independently selectable finishes and paper, linen, metal, wood, and leather substrates
  • Per-finish and per-material defaults editable from Lab and versioned in the repository
  • Procedural rounded-card geometry generated from shared physical dimensions
  • Deterministic linen normal map
  • Card manipulation, camera inspection, zoom, frame-rate-independent flip/reset, interruption-safe sweep, and movable lighting
  • Repeatable front, grazing-angle, edge, and back inspection poses
  • Point, directional, and spot lighting with repeatable studio, gallery, window, dramatic, and flat-review presets
  • Live light color, position, intensity, environment contribution, and exposure controls
  • Lab-mode loading of local artwork and finish-mask images without rebuilding
  • Optional aligned text-mask assets for readable lettering under substrate relief and material treatments
  • Named browser experiment saves, versioned JSON import/export, and PNG capture
  • Experimental condition wear with substrate-aware edges, scratches, scuffs, and imperfection seeds; wear and imperfections are deferred product features
  • Bare Inspect mode and small Lab-mode parameter panel
  • Pack mode: a touch-responsive foil pouch with a draggable tear strip, reveal-safe ordered stack, and one-at-a-time inspection
  • Drawing-buffer resolution plus median, p95, and worst frame-time display

Transform gizmos and a general timeline are intentionally deferred. The separate Flutter Scene comparison project is unchanged; Three.js remains the authoritative runtime.

Tear-open foil pack prototype

Experimental materials and finishes

The toolbar, Lab controls, saved experiments, and random pack pool share the same available choices. The following treatments are visual trials, not approved defaults:

  • Leather material: shallow, softly rounded grain, restrained warm ink absorption, and a brown edge. Mask-protected ink stays smooth; artwork and lettering stay anchored. The original dense, deeply recessed grain was rejected for poor readability. The current trial uses moderate soft-grain relief (0.0032 scene units at default detail), between the original harsh treatment and the overly faint first revision. Selecting Leather now starts Surface detail at 0.45; other materials start at 0.14. The same Leather default applies to randomized pack cards.
  • Gold leaf finish: warm metallic gilding with fine irregular folds and mottling, rather than ordinary foil's mostly color-preserving sheen. Its gold range and highlights are compressed, with more original ink retained, to reduce contrast without removing the folded texture.
  • Frosted glass finish: cool satin haze and fine etched microrelief, without transparency or blurring the underlying artwork.

Vellum and Pearlescent were removed after visual review because they were not sufficiently distinct. Older experiments using either report an unsupported format.

All finishes follow the supplied finish mask, including text protection. Leather also uses the mask to suppress grain over protected ink. Other material textures cover the substrate independently of the finish mask. An optional text mask additionally protects lettering from substrate relief and high-contrast treatments. Finish strength controls the new coatings; Surface detail controls the material relief across 0-1.0 in Lab and saved experiments. Existing relief caps keep parallax bounded at high values. Reset returns to Paper with detail 0.14; importing an experiment retains its saved detail up to 1.0. No card-specific regions, artwork generation, or additional texture files are required.

Choose Pack in the Mode selector (expand Controls first on mobile). Use Pack style in the same toolbar to compare three wrapper-only design studies:

  • Cathedral glass: deep blue, geometric stained-glass window artwork, and restrained gold.
  • Illuminated manuscript: warm ivory, burgundy, and botanical manuscript linework.
  • Quiet modern (initial selection): dark green, generous space, and a small sacred emblem.

All three use the same wording, foil material, pouch geometry, lighting, card contents, and opening animation. These are alternative visual studies, not different pack tiers or reward odds. Switching styles changes the front, back, and matching tear strip in place, preserving the current tear/inspection state. Use Restart to compare sealed packs; the chosen style is retained across Restart and mode switches, but not a page reload. On mobile, reopen Controls after entering Pack to access the selector. The six front/back textures are generated once and uploaded during pack preparation, then reused for instant switching. No illustration service or external asset is needed.

Drag the gold top seam to the right, or use Tear open for the same authored opening without dragging:

Sealed → progressive tear → strip curls away → mouth spreads → staggered card extraction → face-down stack → lifting → lifted → revealing → inspecting → advancing → next stack / complete.

Lifting and flipping are separate actions so the universal back can be reviewed before revealing. The authored order is always David / Printed ink / Linen, Timothy / Foil / Paper, then David / Holographic / Metal. This is not a randomized reward system.

  • The seam has a mouse/touch hit area at least 44 px tall. Drag distance drives tear progress monotonically: pulling back does not reseal it. A full pull completes the opening on release; a short pull holds its exact tear and Finish opening continues from there. The progress bar and percentage also expose the state without relying on the animation.
  • Cancellation or lost pointer capture holds a partial tear; another seam pull, Finish opening, or Skip can continue it. A second finger relinquishes the tear gesture to pinch zoom. Restart and mode changes release all captured pointers.
  • Pressing the pouch makes a small local dent that settles on release. Creases and seam crimps are deterministic, not continuously moving noise.
  • Drag to rotate and pinch or wheel to zoom during inspection. Pinch/wheel zoom also works before revealing; hidden cards cannot be manually tilted. User zoom may intentionally crop the composition.
  • Touching or zooming during a scripted transition pauses at the current pose. Continue resumes from that elapsed point, without snapping. A tap that interrupts motion does not also advance it.
  • Skip finishes only the current transition, or performs one step immediately from a resting state. It never skips an unseen card's reveal. Restart restores the sealed wrapper and first card.
  • Reduced-motion preference shortens each transition to 70 ms with the same state and reveal/completion ordering.
  • Switching away pauses Pack and preserves the Inspect/Lab card, camera, materials, local images and experiment settings. Returning to Pack resumes its retained state via Continue.

The foil wrapper is two subdivided, joined pouch surfaces and a separate subdivided top ribbon. Controlled vertex deformation creates shallow creases, a travelling tear front, a curled detached strip, and spreading lips. Typography is printed directly on the sheet materials, not on an almost-coplanar label layer. There is no rigid-panel split, transparent cross-fade, cloth engine, or extra asset dependency. The pouch stays above the floor while the three cards slide upward with a small stagger. Only after all cards clear the mouth does the empty pouch move aside; the cards then settle into the existing face-down stack. Opening-only camera framing widens and recenters during extraction, then returns to the established stack framing.

Cards use the existing procedural geometry and approved runtime shader. Before revealing, fronts are hidden, backs share the fixed universal material, and edges use a neutral paper recipe. Authored substrate edges are enabled only after the flip finishes. The active card lifts to a dedicated inspection plane before flipping or free rotation. The reserve stack stays visible, offset to the lower right and safely behind the active card's full corner-sweep volume. The exit animation also stays on that safe plane while the reserve stack recenters. Camera framing eases with the lift/exit so the deeper separation does not unexpectedly enlarge the card. The edge mesh draws only extrusion side walls, not end caps beneath the dedicated artwork faces, preventing depth-fighting bands at farther zoom distances. Pack assets are loaded separately on first entry, with retry on failure. Before enabling pack interaction, textures are uploaded and shaders are compiled for the pouch, hidden card fronts, universal backs, and both neutral/revealed edge materials using the scene's lighting and environment. This preparation does not draw or reveal cards. The loading state lasts until it finishes; failures release the new pack resources and keep retry available. Returning to an already prepared pack retains its state without repeating this preparation.

Wrapper deformation is coalesced to once per rendered frame, including touch dents and tear input. Fixed crease/crimp calculations are cached; unchanged pouch surfaces and ribbons do not recalculate normals/bounds or upload vertex buffers. Hidden wrappers do not deform. Pack UI updates are also coalesced and only write changed values. These optimizations retain the same mesh resolution, deformation formulas, materials, lighting, pixel ratio, and antialiasing. Mint cards (condition = 1) bypass procedural wear calculations whose contribution is zero. Worn cards use the original wear equations, and all finish/substrate shading remains unchanged.

There is no sound, haptics, particles, cloth simulation, backend, rewards persistence, pack progress persistence, or timeline/editor. This is a choreography proof, not an final pack-design or mobile performance sign-off. The approved rigid prototype and artifact-free card inspection are the preserved foundation; this foil treatment is the next review iteration. The canvas emits packreveal (one-based index and variant) and packcomplete (count), once per card/pack per restart.

Finish and substrate are separate:

  • Finish controls printed ink, foil, or holographic coating.
  • Substrate controls surface roughness/microtexture, underprint response, and edge/core appearance.

Roughness now responds throughout its 0.05-0.8 range: the slider contributes 75% of effective roughness and a modest substrate bias contributes 25%. This replaces the floors/ceilings that previously left much of the slider inert.

Paper uses fine, irregular fibers and a broad, weak highlight. Linen uses raised-looking interlaced threads with analytic slope lighting, a bounded two-sample procedural parallax offset, and broad ridge highlights that remain visible in Printed ink at oblique angles. Only the weave shifts with the view: artwork and lettering stay anchored. Surface detail scales the effect; unresolved threads fade to avoid shimmer. This is shader relief with no added geometry or change to the card's silhouette, back, or thickness. This linen look was visually approved September 10, 2026 as linen-relief-v1-2026-09-10; its exact defaults are recorded in CURRENT-DEFAULTS.md. No extra toggle or configuration is required: selecting Linen applies it in Inspect, Lab, and the authored pack. The legacy Plastic shader recipe remains readable by older saved experiments but is not offered in the UI or randomized packs. Wood uses raised longitudinal grain with bounded procedural parallax, stronger relief, and a restrained warm tint. The artwork stays anchored while grain shading and tint shift together. Metal uses a color-preserving reflective ink tint and luminance-driven recesses. Its September 12 tuning retains dark ink for stronger contrast and adds fine, irregular vertical brushed-aluminum lines by default, with independently filtered relief and shading. In Lab → Surface, enable Horizontal metal grain to compare against the prior horizontal treatment; disabling it returns to Vertical. The checkbox affects Metal only, persists when changing front finishes/materials, and is saved with experiments. Reset and pack cards use Vertical; old experiments without the orientation field restore Horizontal for compatibility. Unresolved brush lines fade to limit shimmer. This tuning awaits visual review; the retained depth foundation was approved September 10. Bounded parallax shifts the artwork and finish mask together, with directional groove shading to emphasize depth. The effect fades at the card edges. Metal and wood depth were visually approved September 10, 2026 as metal-wood-relief-v1-2026-09-10. Surface detail scales the relief; neither material adds geometry or changes the card silhouette. Procedural detail fades below pixel resolution to limit shimmer. The universal card back uses fixed neutral material properties so front finish and substrate choices do not spoil a pack reveal. The holo spectrum and coverage rules are independent of these substrate changes. Metal combined with Foil preserves the printed colors while adding restrained, slightly raised champagne-metal polishing over the etched steel base.

Reference

npm run assets creates public/reference/manifest.json, copies the David and Timothy regression artwork and universal card back from the project root, and generates the two finish masks and linen normal map. Startup and production builds run this automatically. No spike assets are required.

Inspect/Lab and Pack discover approved card packages directly from ../artifacts/cards. The development server exposes only allowlisted runtime PNGs through a contained asset route; no copies are required in public/card-art. The generated David and Timothy files in public/reference remain shader-regression fixtures. The app uses the generated universal back and linen normal map. Card geometry is generated directly in Three.js from the dimensions recorded in the manifest.

The toolbar defaults to High and exposes Low, Med, High plus all four printings. The Card button opens a searchable, rarity-filterable accepted-card drawer whose lazy thumbnails always use Low/Normal. Pack supports the same resolution and printing selection. It streams only the current and next front, sharing duplicate assets and releasing outgoing textures; hidden High fronts are not loaded en masse.

The current runtime reference revision is runtime-look-v4-2026-09-07. The approved linen, metal, and wood depth follow-ups change runtime shader behavior, not the source artwork or generated normal map. The asset manifest therefore retains its v4 reference revision; the scoped material approvals and parameters are tracked in CURRENT-DEFAULTS.md.

David uses color-based coverage and title-panel protection. Holographic color and angle response are preserved; reduced sheen applies only to foil.

Run

npm install
npm run dev

Production build:

npm run build

Pack regression checks (Node 22.15+):

npm test

These also check full ten-card reveal/completion, shared-geometry cleanup, idle draw scheduling and sampling, Metal orientation defaults, save/import round trips, legacy experiment compatibility, uniform-only toggles, and pack defaults. They check exact pre-optimization wrapper geometry snapshots, surface update counts, per-frame input batching, pause/resume, pinch handoff, restart, reduced motion, ordered reveals, GPU preparation, and failure cleanup. They are CPU/state checks, not a real-device GPU or touch-latency benchmark; mobile performance still needs on-device validation.

WebGL pixel parity and pack shader-preparation checks require a local headless Chromium browser with WebGL2 support, installed project dependencies, and generated reference assets (npm run assets):

SHADER_BROWSER=/absolute/path/to/chromium \
SHADER_BROWSER_KIND=chromium \
node scripts/shader-pixel-regression.mjs

The Chromium runner uses SwiftShader software WebGL. Browsers are not installed automatically; a missing browser, missing WebGL support, timeout, pixel mismatch, or unexpected shader program causes a nonzero exit rather than a skipped check.

The pixel check compares 360 combinations of substrate, finish, condition, angle, and lighting against the current shader with unconditional wear evaluation, verifying that the mint fast path remains equivalent. A separately hash-verified v4 reference checks that Paper and Plastic remain unchanged and that the approved Linen, Metal, and Wood iterations produce a visual difference. The pack check exercises opening, all ten selected reveals, and restart under point, directional, and spot lighting, checking for new shader programs after preparation. Software WebGL can validate pixel parity and program reuse, but its timings are not mobile GPU benchmarks.

The asset-generation script uses cross-platform Node APIs and works on Windows and Linux. The retained export:blender script is an optional reference utility and is not part of the application pipeline.

Baseline

Initial development machine:

  • Windows
  • Intel Arc Graphics, driver 32.0.101.6737
  • Node 24.14.1
  • Three.js version pinned by package-lock.json

The browser/version, viewport, drawing-buffer resolution, texture settings, and observation duration must be recorded during material approval. The on-screen frame-time display is diagnostic rather than a complete benchmark.

Optional text masks

Accepted Normal and Borderless packages provide text-mask.png beside card.png and finish-mask.png in each resolution/printing directory. It must match artwork dimensions and be opaque grayscale: white protects lettering, black retains the material. Generate it from the rendered text layer with a small, recorded feathered margin; it is separate from finish coverage and card silhouette alpha.

The catalog exposes textMask only when the optional file exists, validates it, and includes its bytes in cache revisions. Inspect/Lab and selected pack cards load it as data automatically. An absent map makes no extra request and retains the existing shader path. An advertised map that fails validation/loading reports an error rather than silently falling back. Replacing local artwork clears an old fixture's text mask to avoid misregistered protection.

One optional texture sample, anchored before Metal parallax, reduces substrate height/normal perturbation and finish coverage. Protected text blends toward plain diffuse ink lighting, suppressing Metal recess shadows, brushed detail, substrate tint, and specular glare. Black regions retain their existing treatment. There is no postprocess, added geometry, changed font renderer, or AA change.

The separate Calling the Disciples P052 proof contains actual glyph-derived masks, P052 Roman cards with Matthew 4:19 • NKJV, reproducible Metal comparison experiments, and structural validation. Its README lists the runtime fixture names and review steps. Moving-light readability and rendered pixel-equivalence checks still require a working WebGL context.

Rare Burning Bush fixture

burning-bush-rare-normal-1000 is a new P052 Roman Rare proof with aligned optional text protection. The published artwork, finish mask, and text mask share this exact filename stem. Category and rarity metadata, retained illustration, editable Rare frame, full printing/layer reviews, reproducible Metal snapshot, and validation reports live in burning-bush-rare-proof. It prints Exodus 3:6 • NKJV in the canonical reference format and uses the shared non-Legendary text/backing palette. Moving-light material appearance awaits a working WebGL review.

Performance comparisons

See the performance improvement outline for current source findings, implementation priorities, benchmark cases, and check results. It also records the current ten-card randomized pack behavior; the earlier three-card choreography description above is historical.

The harness renders at the full device pixel ratio up to the existing cap of 2. Fast/Balanced canvas scaling was removed after visual review because the loss of artwork and text clarity was unacceptable. Future support for lower-resolution card assets is deferred. Current performance work should preserve the approved rendering quality and prioritize texture sharing, Pack batching, and measured shader costs.

Settled scenes stop issuing GPU draws while animation callbacks remain available for input and motion. The display labels idle periods and retains the last sampled motion timings; idle gaps are excluded. Timing windows reset on mode changes. ResizeObserver handles canvas layout changes. Pack cards share two geometry buffers while retaining their individual transforms and materials.

Diagnostic readouts and timing-recording controls are hidden by default. Show diagnostics in the toolbar reveals them; the current card/material status remains visible either way.

The diagnostic display also reports median CPU scene-update/render-submission time, draw calls, resident renderer texture count, and shader program count. CPU submission time can include driver waits; it is not a direct GPU timing measurement. Resource counts are not GPU memory bytes. A second diagnostic line reports idle/drawing callback medians, asynchronous GPU render time when supported, device pixel ratio, MSAA sample count, and the reported GPU identity. GPU timing samples up to five draws per second, checks results in later callbacks, and rejects disjoint results. It reports unavailable when the browser exposes no usable timer; it does not substitute CPU submission timing. It excludes presentation/compositing.

Card faces now cull inward-facing triangles; the wrapper remains double-sided for its curls/opening. Uniform-only edge changes reuse the material shader version. The overlay no longer blurs the animated canvas beneath it.

Pack wrapper, geometry, and placeholder resources stay resident after switching modes to preserve state. Catalog entries alone do not load artwork: Inspect/Lab load the selected variant, while Pack keeps at most the current and next front acquired.

For a useful comparison, keep viewport, card, pose, lighting, and motion identical; allow at least four seconds for the 240-frame window to turn over at 60 Hz (longer at lower frame rates). Compare CPU update/submission, GPU render time where available, and idle/drawing callback pacing while sweeping or rotating. Compare Paper with Metal/Linen at the same resolution to isolate material cost. Compare Inspect before and after first entering Pack to investigate retained-resource pressure. First-entry pack loading/compilation is separate from steady animation performance. Record browser, GPU, drawing-buffer size, and median/p95 alongside each observation.

Exportable frame-pacing logs

Choose Record timings in the toolbar (expand Controls on mobile). Each recording runs for 30 seconds, or until Stop recording or Export timings is pressed. Export timings downloads card-harness-timings-<timestamp>.json. A new recording replaces the previous recording; reload clears it.

For the 33 ms investigation, remain idle for five seconds, play the sweep and rotate/flip the card, then leave it idle again and export. Make a separate Pack recording covering idle, opening, reveal, and inspection. Use the same viewport and keep browser tools closed during the recording; inspect the exported JSON afterward.

The JSON contains browser/GPU/context details, full drawing-buffer settings, callback timestamp and actual-arrival intervals, idle-versus-drawing pacing, actual draws, CPU update/submission and whole-callback work, per-phase summaries, resource counts, delayed GPU samples linked to their issue frames, and a bounded sequence of state/input/focus/visibility/context events. Long-task and long-animation- frame observations are included where supported; unsupported observers/timers are identified in metadata. GPU timing excludes presentation/compositing. Short CPU measurements can be quantized by browser clock precision, so zero is not no work.

Idle callbacks near 33 ms while no draws occur point toward pacing beyond scene rendering, including browser/display policies. Idle callbacks near 16.7 ms with slower drawing callbacks and high GPU elapsed time point toward rendering cost. These are investigation leads, not automatic diagnoses. Visibility transitions break sampling continuity and remain logged; genuine stalls are retained rather than silently filtered out.

Recording retains at most 7200 callbacks, 256 events, and 256 GPU results. Dropped events are counted. Observers stop with the recording; there is no per-frame console output, synchronous GPU wait, or logging overhead from storing samples when recording is off. The normal HUD and sampled GPU diagnostics still run.