/** * draw-spec — pure-data scene extraction for the native render path (D.083, task 2h3s). * * The PRE-MORTEM verdict: `ECSWorld → IDrawCall[]` is NOT a connector — IDrawCall is * GPU-resource-bound (GPUBuffer vertex data, GPUBindGroup material) and requires a live * GPUDevice. So this module is deliberately the GPU-FREE half: it extracts pure data * (geometry kind + params, material values, model matrix) from the canonical engine VM * world. It is headless-unit-testable (no `navigator.gpu`, no Three.js) and is the only * part that belongs in CI. The GPU resource builder (DrawSpec[] + GPUDevice → IDrawCall[]) * is a SEPARATE, additive, opt-in backend gated on a browser-run receipt — NOT here. * * Imports only the engine VM's own world/types — zero foreign-engine imports. */ import { ECSWorld } from '../vm/executor'; export type GeometryKind = 'cube' | 'sphere' | 'cylinder' | 'plane' | 'cone' | 'torus' | 'capsule' | 'mesh' | 'unknown'; /** Pure-data reference to a texture map — either the ENCODED image bytes (e.g. PNG/JPEG, as * embedded in a glTF image bufferView) + MIME type, or an external URI. No decoded pixels and * no GPU resources: a later stage / the GPU backend decodes + uploads. (Track 0: the `.holo` * material IR can carry a real imported texture map, not only packed scalars.) */ export interface TextureRef { /** Encoded image bytes (e.g. PNG/JPEG) when embedded in the source (glTF image bufferView). */ bytes?: Uint8Array; /** MIME type of `bytes` (e.g. 'image/png', 'image/jpeg'). */ mimeType?: string; /** External URI when the source references the image by path instead of embedding it. */ uri?: string; } /** Optional PBR texture maps (glTF model): base color, tangent-space normal, the glTF-combined * metallic-roughness map, and emissive. All optional — absence = the scalar-only material. */ export interface MaterialTextureMaps { albedoMap?: TextureRef; normalMap?: TextureRef; metalRoughMap?: TextureRef; emissiveMap?: TextureRef; } /** Pure-data material (no GPUBindGroup) — the GPU backend resolves this to a pipeline+bind group. */ export interface MaterialSpec { color: number; metalness: number; roughness: number; emissive: number; opacity: number; /** Optional PBR texture maps populated by the glTF importer; undefined for the procedural / * scalar-only material (existing path byte-for-byte untouched when omitted). */ albedoMap?: TextureRef; normalMap?: TextureRef; metalRoughMap?: TextureRef; emissiveMap?: TextureRef; } /** Pure-data draw specification. NO GPU resources — see module header. */ export interface DrawSpec { entityId: number; geometry: { kind: GeometryKind; params: Record; /** * Real imported vertex buffers — present when kind === 'mesh' and a real mesh * was carried through the glTF importer (Track-0 / D.101/ssja). Absent for * procedural primitives (cube/sphere/…) whose CPU geometry is generated by * primitiveToMesh at render time. When present, a native render path can upload * positions/indices directly without the original GLB file. */ meshData?: SkinnedMeshData; }; material: MaterialSpec; /** Column-major 4x4 model matrix (TRS), 16 floats. */ modelMatrix: Float32Array; } /** * Skinned mesh geometry as pure data (no GPU). The primary palette index + weight preserve * the original rigid and imported-mesh ABI. Procedural profiles may opt into a second scalar * influence for normalized joint transitions without widening every legacy vertex. */ export interface SkinnedMeshData { positions: Float32Array; normals: Float32Array; /** 4 floats/vertex: xyz strand-flow tangent (Kajiya-Kay) + w = strandT (0 root → 1 tip). * Body verts carry a placeholder (0,1,0,0); only hair/anisotropic materials read it. */ tangents: Float32Array; /** Optional UV coordinates, 2 floats/vertex (glTF TEXCOORD_0). Populated by the glTF * importer when the source primitive carries texture coordinates; undefined for the * procedural body (which has no UVs). Prerequisite for textured / material-mapped imported * meshes — Track 0: the `.holo` mesh IR carries a real imported surface, not a text pointer. */ uvs?: Float32Array; indices: Uint32Array; jointIndices: Uint32Array; jointWeights: Float32Array; secondaryJointIndices?: Uint32Array; secondaryJointWeights?: Float32Array; vertexCount: number; } /** * Pure-data CHARACTER draw spec — kept SEPARATE from the primitive `DrawSpec` so the * existing primitive render path is untouched (premortem regression guard). The native * WebGPU character renderer uploads the mesh + the per-frame joint-matrix palette and skins * on-GPU. `jointMatrices` = jointCount × 16 column-major floats (skin = worldPose · invBind). */ export interface CharacterDrawSpec { entityId: string; mesh: SkinnedMeshData; jointMatrices: Float32Array; jointCount: number; /** Required fallback material applied to the whole mesh when `materialGroups` is absent. */ material: MaterialSpec; /** Column-major 4x4 root placement matrix (world position/orientation of the figure). */ modelMatrix: Float32Array; /** * Optional per-region materials (glTF-primitive model): contiguous index sub-ranges, each * drawn with its own shading pipeline in the SAME render pass + shared skin palette. When * present and non-empty, used INSTEAD of `material` (which stays the single-draw fallback — * the existing path is byte-for-byte preserved when this is omitted). */ materialGroups?: MaterialGroup[]; } /** Which fragment shading model a material group renders with. */ export type ShadingModel = 'lambert' | 'skin-sss' | 'marschner-hair' | 'refractive-eye' | 'woven-cloth'; /** Flat two-sided half-Lambert (the Phase-0 base; the single-material fallback). */ export interface BaseMaterialSpec extends MaterialSpec { shadingModel: 'lambert'; } /** Single-pass real-time skin: per-channel wrap diffuse + scatter tint + transmission + Fresnel. */ export interface SkinSSSMaterialSpec extends MaterialSpec { shadingModel: 'skin-sss'; /** Subsurface tint, RGB 0..1 (reddish for skin). */ scatterColor: [number, number, number]; /** Per-channel relative scatter radius (red widest → leaks furthest past the terminator). */ scatterRadii: [number, number, number]; /** Fresnel F0 (skin ≈ 0.028). */ specularF0: number; /** 0 = thin/translucent, 1 = thick/opaque (drives back transmission). */ thickness: number; /** Back-scatter transmission strength. */ transmitStrength: number; /** Ambient floor. */ ambient: number; /** Provider-independent analytic surface detail. Omission preserves the smooth legacy path. */ microdetailProfile?: 'none' | 'analytic-pore-v1'; /** Analytic pore frequency in inverse metres (20..180 when enabled). */ microdetailScale?: number; /** Bounded roughness/normal-response amplitude (0..0.2). */ microdetailStrength?: number; /** * Opt-in decoupled analytic skin response. Omission preserves the legacy path where * `microdetailStrength` drives roughness and a smaller albedo variation together. */ surfaceResponseProfile?: 'calibrated-skin-surface-v1'; /** Independent analytic base-colour variation amplitude (0..0.08). */ albedoVariationStrength?: number; /** Independent analytic microsurface roughness variation amplitude (0..0.2). */ roughnessVariationStrength?: number; /** Tangent-plane analytic normal perturbation amplitude (0..0.35). */ normalMicrodetailStrength?: number; /** Opt-in bind-space facial colour response. Omission preserves the uniform legacy albedo. */ complexionProfile?: 'anatomical-complexion-v1'; /** Bounded influence of cheek, nose, under-eye, lip, and jaw colour zones (0..1). */ complexionStrength?: number; /** Opt-in UV-driven portrait breakup over the analytic skin response. */ textureSpaceProfile?: 'portrait-texture-space-v1'; /** Bounded contribution of the UV-space albedo and microsurface signal (0..1). */ textureSpaceStrength?: number; } /** Source-authored hair-card edge treatment. `opaque-v1` preserves the legacy solid-card path. */ export type HairCoverageProfile = 'opaque-v1' | 'alpha-to-coverage-v1'; /** Kajiya-Kay anisotropic hair with melanin→colour (needs tangent + card-width UV attributes). */ export interface MarschnerHairMaterialSpec extends MaterialSpec { shadingModel: 'marschner-hair'; melanin: number; melaninRedness: number; /** * Weight of source-authored RGB chroma over the melanin response. Zero preserves the * historical melanin-only path; authored @hair(color) uses a bounded non-zero weight. */ sourceColorWeight?: number; primaryExp: number; secondaryExp: number; /** Analytic card-width coverage; alpha-to-coverage requests a multisampled render target. */ coverageProfile: HairCoverageProfile; /** Visible half-width of a hair card in normalized edge space (0..1). */ strandCoverage: number; /** Width of the analytic edge transition (0.01..0.5). */ edgeSoftness: number; /** Blend from normal-based specular to strand-tangent anisotropy (0..1). */ anisotropyStrength: number; /** Tangent/normal longitudinal lobe shift (-0.35..0.35). */ longitudinalShift: number; /** Opt-in layered density/root response over the alpha-to-coverage card path. */ densityProfile?: 'layered-card-density-v1'; /** Layered-card opacity modulation; operative only for layered-card-density-v1. */ densityStrength?: number; /** Root darkening/occlusion response; operative only for layered-card-density-v1. */ rootShadowStrength?: number; } /** Refractive eye: iris/sclera blend + pupil + wet specular catchlight + Fresnel rim. */ export type RefractiveEyeRegion = 'composite' | 'sclera' | 'iris' | 'pupil' | 'cornea'; export interface RefractiveEyeMaterialSpec extends MaterialSpec { shadingModel: 'refractive-eye'; /** Cornea index of refraction (~1.376) — drives the Fresnel rim strength. */ ior: number; /** Native geometry/material role. Omission preserves the legacy composite eye shader. */ eyeRegion?: RefractiveEyeRegion; } /** * Compact local texture tile carried as pure data. Four-by-four texels keep character draw * specs deterministic and provider-independent while proving authored UV/material-map flow. * Full-resolution imported textures remain the glTF material-map path. */ export interface WovenClothTextureTile { size: 4; /** Sixteen authored luminance values, row-major, multiplied into the base colour. */ albedo: number[]; /** Thirty-two authored tangent-space XY values (0..1), row-major pairs. */ normalXY: number[]; /** Sixteen authored roughness values, row-major. */ roughness: number[]; /** UV repeats over the surface, clamped by the renderer to 1..16. */ repeat: number; /** Tangent-space normal response, 0..2. */ normalScale: number; } /** Rough woven cloth with grazing-angle sheen and optional local UV-mapped material detail. */ export interface WovenClothMaterialSpec extends MaterialSpec { shadingModel: 'woven-cloth'; /** Broad highlight width, 0.05..1. */ roughness: number; /** Retroreflective fibre sheen, 0..1. */ sheen: number; /** World-space weave frequency in cycles/metre. */ weaveScale: number; /** Cloth rim response at grazing angles, 0..1. */ rimStrength: number; /** Optional source-resolved local texture tile sampled through mesh UVs. */ textureTile?: WovenClothTextureTile; } export type CharacterMaterialSpec = BaseMaterialSpec | SkinSSSMaterialSpec | MarschnerHairMaterialSpec | RefractiveEyeMaterialSpec | WovenClothMaterialSpec; /** * Semantic role of one native character material group. * * The renderer does not branch on this value: it is source/proof metadata that * lets receipts distinguish, for example, an SSS skin draw from an SSS-derived * keratin nail draw without guessing from colour or roughness. */ export type CharacterMaterialRole = 'skin' | 'keratin-nail' | 'nail-bed' | 'hair' | 'eye' | 'sclera' | 'iris' | 'pupil' | 'cornea' | 'garment' | 'visor' | 'mantle' | 'fallback'; /** A contiguous slice of `mesh.indices` drawn with one material. Offsets are INDEX ELEMENTS. */ export interface MaterialGroup { /** First index (element offset into mesh.indices) — passed as drawIndexed firstIndex. */ indexStart: number; /** Number of indices in this group. */ indexCount: number; material: CharacterMaterialSpec; /** Source-authored semantic role, retained in native renderer receipts. */ materialRole?: CharacterMaterialRole; /** Draw after opaque groups with blend + depthWrite off (e.g. refractive eyes). */ transparent?: boolean; } export declare function geometryKindFromType(type: number): GeometryKind; /** * Compose a column-major 4x4 TRS matrix from position / rotation quaternion / scale. * Pure math — matches glMatrix fromRotationTranslationScale convention (WGSL column-major). */ export declare function composeTRS(t: { x: number; y: number; z: number; }, q: { x: number; y: number; z: number; w: number; }, s: { x: number; y: number; z: number; }): Float32Array; /** * Extract pure-data draw specs from the canonical engine VM world. Renderable = alive + * has Transform + Geometry + Material. Deterministic order by entity id. NO GPU, NO Three. */ export declare function extractDrawSpecs(world: ECSWorld): DrawSpec[]; //# sourceMappingURL=draw-spec.d.ts.map