Files
Sanctification/card-harness/README.md

393 lines
25 KiB
Markdown

# Sanctification Card Lab
Bounded Three.js proof for the card-rendering harness plan.
The approved visual baseline is recorded in [`CURRENT-DEFAULTS.md`](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`](CURRENT-DEFAULTS.md#linen). 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
```sh
npm install
npm run dev
```
Production build:
```sh
npm run build
```
Pack regression checks (Node 22.15+):
```sh
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`):
```sh
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](../calling-disciples-text-mask-proof/README.md)
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](../burning-bush-rare-proof/README.md). 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](PERFORMANCE-PLAN.md) 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.