/** * character-render — native WebGPU skinned-character render to verified pixels. * * The character sibling of `native-render/scene-render.ts`: takes a pure-data * `CharacterDrawSpec` (skinned mesh + per-frame joint-matrix palette), uploads geometry + * palette to a live GPUDevice, runs the `skin-skinning.wgsl` GPU-skinning module, and reads * pixels back. NO Three.js, NO R3F. * * Multi-material via MATERIAL GROUPS (glTF-primitive model): one shared vertex/index/palette, * contiguous index sub-ranges each drawn with its own shading pipeline in ONE render pass. The * shared frame state (camera/light/palette) binds once (@group0); only the per-group Material * (@group1) changes per draw. When `spec.materialGroups` is absent the renderer falls back to a * single full-mesh lambert draw — behaviorally identical to the pre-material-groups path. * * This is BOTH the production render entry AND the headless verification/fallback floor * (G.GOLD.006): it runs under Dawn in CI and on a real browser device identically. * * @module character-render */ import type { CharacterDrawSpec, CharacterMaterialRole, CharacterMaterialSpec, ShadingModel } from '../native-render/draw-spec'; import type { PixelGrid } from '../native-render/gpu-verify'; import { type Mat4 } from './skin-math'; /** * Orthographic front-view projection that frames a standing humanoid (looking down -Z, world * +Z toward the viewer → smaller depth). Centers a `heightScale`×1.75 m figure vertically. * Column-major; maps world z∈[-D,D] → clip depth [0,1] for the depth test. */ export declare function framingMatrix(heightScale?: number): Mat4; export interface CharacterRenderOptions { /** Square framebuffer edge (default 128 → bytesPerRow 512, 256-aligned). */ size?: number; /** Override the view·projection (defaults to framingMatrix for a standing figure). */ viewProj?: Mat4; /** World-space light direction (default upper-front-right). */ lightDir?: [number, number, number]; /** Optional analytic environment authored by @environment_light. Omission is byte-compatible legacy key light. */ environmentLight?: CharacterEnvironmentLightOptions; /** World camera position (for view dir / Fresnel; default in front on +Z for the ortho view). */ cameraPos?: [number, number, number]; /** Clear RGBA, 0..1 (default dark). */ clear?: [number, number, number, number]; heightScale?: number; } export type CharacterEnvironmentLightProfile = 'legacy-key-v1' | 'analytic-three-point-v1' | 'directional-reflection-probe-v1' | 'stormglass-room-basis-v2'; export interface CharacterEnvironmentLightOptions { profile?: CharacterEnvironmentLightProfile; keyDirection?: [number, number, number]; keyColor?: [number, number, number]; keyIntensity?: number; fillDirection?: [number, number, number]; fillColor?: [number, number, number]; fillIntensity?: number; rimDirection?: [number, number, number]; rimColor?: [number, number, number]; rimIntensity?: number; exposure?: number; } export interface CharacterEnvironmentLightReceipt { schemaVersion: 'holoscript.character-environment-light.v1' | 'holoscript.character-environment-light.v2' | 'holoscript.character-environment-light.v3'; profile: CharacterEnvironmentLightProfile; /** H3Y reinterprets the three authored lobes as a low-frequency reflection probe. */ responseProfile?: 'three-lobe-diffuse-specular-probe-v1' | 'source-authored-room-basis-v2'; /** H3Z is a procedural room basis, not a photographic or imported HDRI. */ photographicHdri?: false; key: { direction: [number, number, number]; color: [number, number, number]; intensity: number; }; fill: { direction: [number, number, number]; color: [number, number, number]; intensity: number; }; rim: { direction: [number, number, number]; color: [number, number, number]; intensity: number; }; exposure: number; } /** Resolve the exact analytic lights uploaded to the native WebGPU frame uniform. */ export declare function deriveCharacterEnvironmentLightReceipt(options?: CharacterEnvironmentLightOptions, legacyLightDirection?: [number, number, number]): CharacterEnvironmentLightReceipt; export interface CharacterVertexRange { vertexStart: number; vertexCount: number; } export interface CharacterDetailFrameOptions { /** Empty space around the selected bounds (default 1.35). */ padding?: number; /** Lower bound on the square half-extent in world units (default 0.04). */ minHalfExtent?: number; /** Symmetric world-space depth half-extent used for clip depth (default 1.5). */ depthHalfExtent?: number; } export interface CharacterDetailFrameReceipt { schemaVersion: 'holoscript.character-detail-frame.v1'; vertexRangeCount: number; selectedVertexCount: number; bounds: { min: [number, number, number]; max: [number, number, number]; }; center: [number, number, number]; halfExtent: number; padding: number; matrix: Mat4; } /** * Derive a deterministic square orthographic close-up from source-owned mesh * vertex ranges. This keeps detail plates tied to native geometry instead of * hand-tuned screenshots. */ export declare function deriveCharacterDetailFrame(mesh: CharacterDrawSpec['mesh'], vertexRanges: readonly CharacterVertexRange[], options?: CharacterDetailFrameOptions): CharacterDetailFrameReceipt; /** Pack a CharacterMaterialSpec into the 84-float Material uniform (see skin-skinning.wgsl). */ export declare function packCharacterMaterial(m: CharacterMaterialSpec): Float32Array; export interface CharacterRenderPipelineReceipt { schemaVersion: 'holoscript.character-render-pipeline.v1'; sampleCount: 1 | 4; alphaToCoverageEnabled: boolean; alphaToCoverageGroupCount: number; } export interface CharacterMaterialGroupReceipt { drawOrdinal: number; materialRole: CharacterMaterialRole; indexStart: number; indexCount: number; shadingModel: ShadingModel; color: number; roughness: number; transparent: boolean; scatterColor?: [number, number, number]; scatterRadii?: [number, number, number]; specularF0?: number; thickness?: number; transmitStrength?: number; ambient?: number; } export interface CharacterMaterialPlateReceipt { schemaVersion: 'holoscript.character-material-plate.v1' | 'holoscript.character-material-plate.v2'; rendererEntrypoint: 'renderCharacter'; backend: 'webgpu'; sourceMaterialGroups: boolean; deviceExecutionMeasured: false; scheduledDrawCount: number; roleCounts: Partial>; skinIndexCount: number; /** Compatibility alias for keratinIndexCount. */ nailIndexCount: number; keratinIndexCount: number; nailBedIndexCount: number; nailSurfaceIndexCount: number; skinNailOverlapIndexCount: number; skinNailBedOverlapIndexCount: number; nailBedKeratinOverlapIndexCount: number; nailSeparatedFromSkin: boolean; nailBedSeparatedFromKeratin: boolean; calibratedNailSurface: boolean; groups: CharacterMaterialGroupReceipt[]; } /** * Describe the exact draw schedule used by `renderCharacter`. The receipt is * deliberately pure-data (`deviceExecutionMeasured=false`); callers attach a * GPU-readback witness only after a live device actually renders the plate. */ export declare function deriveCharacterMaterialPlateReceipt(spec: CharacterDrawSpec): CharacterMaterialPlateReceipt; /** Pure-data pipeline derivation used by both tests and the live GPU renderer. */ export declare function deriveCharacterRenderPipelineReceipt(spec: CharacterDrawSpec): CharacterRenderPipelineReceipt; /** * Render a skinned character to an offscreen rgba8unorm texture and read the pixels back. * One depth-tested render pass; GPU-skinned via the joint palette; one draw per material group * (or a single lambert draw when no groups are given). */ export declare function renderCharacter(device: GPUDevice, spec: CharacterDrawSpec, opts?: CharacterRenderOptions): Promise; //# sourceMappingURL=character-render.d.ts.map