Skip to content

feat(locker): in-app 3D hero preview (Source 2 materials, cloth, R3F) - #1

Closed
oldreceipt wants to merge 4 commits into
mainfrom
feat/r3f-viewer-integration
Closed

oldreceipt wants to merge 4 commits into
mainfrom
feat/r3f-viewer-integration

Conversation

@oldreceipt

@oldreceipt oldreceipt commented Jun 20, 2026 •

Copy link
Copy Markdown
Owner

Summary

This brings the Locker's 3D hero preview up to in-game fidelity. The existing preview loaded the posed GLB as flat meshes and discarded the Source 2 material data baked onto it; this PR threads that data end to end (main-process export, IPC, preload, renderer API) and reconstructs Deadlock's NPR look (cel shading, rim light, tint, self-illum, detail, outline) on a single unified material build pass, layered over the IBL + ACES output rather than replacing it. It adds selective bloom for self-illum glows, an ambient-FX sprite descriptor path, and a faithful Source 2 FeModel cloth solver. Selective bloom ships on by default; the heavier NPR/cloth/effect work is wired and tested but parked behind dev flags while it is calibrated.

What's included

Renderer (3D preview)

  • HeroPoseViewer rebuilt on React Three Fiber: surfaces the Source 2 material payload on the posed GLB, holds a slow turntable with a hold-to-pause control, and exposes dev-only Leva controls for the render flags.
  • deadlockMaterial: a single-pass unified material builder that clones each GLTF material into one we own (the GLTF base is never mutated) and decides all Source 2 state (glass / translucent / additive / self-illum / sheen / unlit / jitter) plus the cel + rim + tint + self-illum-scroll GLSL in one wrap.
  • source2NprMaterial: the layered NPR reconstruction path and shared material helpers.
  • dynamicScalar: a no-eval Tier-1 dynamic-scalar evaluator for Source 2 dynamic material expressions.
  • BloomEffect: selective bloom over self-illum + metallic, thresholded in linear space.
  • ParticleEffect + fxDescriptor: the ambient-FX sprite descriptor consumer.

Cloth simulation (parked behind a flag)

  • feModel: TS parser for the Source 2 FeModel (PHYS.m_pFeModel) block, mirroring morphic's decode_fe_model.
  • clothMath + useClothSim: Verlet/XPBD solver fit to the hero preview skeleton.
  • Off by default behind the clothPreviewEnabled flag.

Plumbing

  • Main-process export extended in heroPoseModels.ts (cloth model + ambient FX export), new IPC handlers in ipc/portraits.ts, preload bridge methods, and renderer API wrappers in lib/api.ts (with HeroEffectInfo / ClothModel types).
  • Render-feature resolution split into a heroPoseRenderFeatures sibling module.
  • Preview i18n strings (pose.pauseSpin / pose.resumeSpin).

Tests and docs

  • Unit / harness coverage for every new lib piece: FeModel parser, cloth fit math, solver stability + a rest-pose-oracle regression against a real hero fixture, NPR / unified material reconstruction, the dynamic-scalar evaluator, and the GLTF preview loader. CLAUDE.md updated from "No Tests" to document the Vitest suite.
  • Adds the data-flow / feasibility design doc (docs/3d-preview-effects-feasibility.md) and docs/hero-pose-locker.md; removes two stale handoff/backlog docs from the prior phase.

Notes for reviewers

  • What is dormant in released builds. Most render flags default off: NPR (USE_NPR_PREVIEW), NPR outline, broad Source 2 shader hints, rigged/animated preview (USE_RIGGED_PREVIEW), and cloth (USE_CLOTH). What is actually live by default is the unified material build pass, cel-v2, and selective bloom. The dormant paths are fully wired and tested so they can be flipped on per material/hero as they are calibrated; flags can be toggled at runtime via localStorage for dev iteration.
  • Cross-repo coupling with vpkmerge (morphic). The cloth and FX data is produced by the bundled vpkmerge binary (model femodel, the FX descriptor export, --pose). feModel.ts is the TS mirror of morphic's decode_fe_model, and the cloth rest-pose semantics are validated against morphic's Rust rest oracle. The bundled version is pinned in scripts/fetch-vpkmerge.mjs. If the cloth/FX wire shape changes on the morphic side, feModel.ts and fxDescriptor.ts must move with it.
  • Dependency additions. Runtime: @react-three/drei, @react-three/postprocessing + postprocessing, three-custom-shader-material. Dev-only: @react-three/test-renderer (for the viewer tests), gltfjsx, and leva (the dev-only viewer controls). three / @react-three/fiber were already present.
  • .gitignore. Ignores the vpkmerge target/ build output and the local .cloth-testbed/ solver scratch dir; neither is meant to be committed.
  • Data-contract doc. docs/3d-preview-effects-feasibility.md is the grounding for the FX axis (renderer-class tiers, the curated ambient roster, and why the raw "ambient candidate" metric over-counts). It is the reference for the dormant effect path, not a claim that the path ships on.

Testing

pnpm lint
pnpm exec vitest run

The full build (pnpm build) needs GRIMOIRE_SOCIAL_BASE_URL set (consumed by electron.vite.config.ts); the lint and Vitest suite above do not require it.

@oldreceipt
oldreceipt force-pushed the feat/r3f-viewer-integration branch from 0ccb17a to 1f1fa6b Compare June 20, 2026 16:48
Add the React Three Fiber stack used by the in-app 3D hero preview: drei,
@react-three/postprocessing + postprocessing, and three-custom-shader-material,
plus the gltfjsx and @react-three/test-renderer dev tooling. leva is a
devDependency (dev-only viewer controls). Ignore the vpkmerge build output
(target/) and the cloth solver visual testbed scratch dir.
…iewEnabled

Decode the Source 2 FeModel (m_feModel) cloth block and run a faithful
Verlet/XPBD solver on the hero preview skeleton: the feModel parser, clothMath
fit helpers, and the useClothSim hook. Covered by unit, harness, and a
rest-pose-oracle regression against a real hero fixture. Off by default behind
the clothPreviewEnabled feature flag.
Reconstruct Deadlock's pbr.vfx NPR look (cel, rim, tint, self-illum, detail,
outline) via a single-pass unified material builder (deadlockMaterial), a
no-eval Tier-1 dynamic-scalar evaluator (dynamicScalar), and the layered
source2NprMaterial path; add selective bloom (self-illum + metallic) and the
ambient-FX sprite descriptor. Each piece is unit-tested.
Add the React Three Fiber hero preview (HeroPoseViewer) and its GLB loader,
wired end to end through the main-process model/effect export, IPC, preload,
and renderer API. Surfaces the Source 2 material payload baked onto the GLB
plus the optional cloth, bloom, and ambient-FX layers, with a hold-to-pause
turntable. Render-feature resolution lives in a sibling heroPoseRenderFeatures
module. Includes the data-flow design doc, removes two stale handoff/backlog
docs from the prior phase, updates CLAUDE.md for the new vitest suite, and adds
the preview i18n strings.
@oldreceipt
oldreceipt force-pushed the feat/r3f-viewer-integration branch from 1f1fa6b to 5222665 Compare June 20, 2026 17:19
@oldreceipt oldreceipt changed the title Ship unified hero preview materials feat(locker): in-app 3D hero preview (Source 2 materials, cloth, R3F) Jun 20, 2026
@oldreceipt

Copy link
Copy Markdown
Owner Author

Superseded by Slush97#235 (cleaned history + proper description).

@oldreceipt oldreceipt closed this Jun 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant