npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@forgeax/engine-shader

v0.3.11

Published

Runtime shader registry — content-addressable manifest lookup → rhi.createShaderModule. Physically isolated from the build-time triple @forgeax/engine-shader-compiler / @forgeax/engine-naga / @forgeax/engine-wgpu-wasm (AC-06 三重闸门 grep 守护).

Readme

@forgeax/engine-shader

Public shader input boundary

The build manifest is the single published shader input catalog. The plugin composes engine WGSL modules and material rows once; runtime resolves the content-addressed result through ShaderRegistry. The shared View module is identified by forgeax_view::common; its typed ABI describes offsets and binding metadata only, never a device or GPU handle. Fog and temporal fields are transported through this existing module/reflection path rather than a second manifest or registry.

The plugin may publish its versioned, shared-source form. ShaderRegistry.loadManifest() checks every source digest and expands it before validating the existing entry and material contracts; failed publication validation leaves the registry unchanged. The runtime does not compile WGSL. Digest verification runs in bounded batches; every source still passes SHA-256 before the expanded manifest can be published. Registries that fetch byte-identical manifest text from the same URL share one verified expansion, held weakly so it lives only while a registry retains it; renderer hosts that stay alive together therefore do not each hold a copy of every composed WGSL source. Changed text is parsed and verified again.

ShaderRegistry.materialProgram(source) admits one immutable program description per composed source within the registry lifetime. The description owns the source, its identity, group-2 contract and ProbeBlend requirement. Registrations, selected variants and material passes share it; forkForDevice() retains these CPU facts while GPU modules are rebuilt. New source selects a new description. The runtime MaterialShaderArtifact.program carries this description instead of independent WGSL, identity and optional binding-decision fields. Synthetic producers use createMaterialShaderProgram() when constructing an artifact, never while drawing.

The published ray diffuse composite and Standard environment lighting share standardDiffuseWeight in forgeax_pbr::ibl_shared. The traced input is unit-receiver D = E/pi; the composite applies the G-buffer receiver color, metallic/Fresnel response and material occlusion once, then adds only RGB to the existing linear HDR target. It preserves direct lighting, emission and coverage.

Atmospheric transport

forgeax_atmosphere::optics owns spherical bounds, densities, normalized phases, solar transmission and segment integration. Sky View, finite-depth aerial perspective and capture/direct rays share this kernel. Local geometry/cloud visibility multiplies the single-scattering source only. A separate low-frequency multiple-scattering LUT supplies the high-order approximation. Radiance LUTs use unit-white-sun values; consumers apply solar colour and lux once. RGB transmittance is independent of alpha. The homogeneous far endpoint preserves view-ray direction at orbital coordinates and supports infinite far planes.

The 1,280-byte View carries six medium vectors and one control vector after the existing fog lanes. Material capability storage-buffer-atmosphere selects the cooked atmosphere bindings; the receiving device must admit the full texture budget. Utility program defines are build-time inputs too. Runtime stays compiler free, including cloud transport/resolve and custom Surface material programs.

view_fog composes extra height fog over physical AP. Straight alpha uses TC+L, premultiplied alpha uses TC+alpha*L, and additive uses TC. Transmission keeps its already-fogged backdrop separate: T*C_local + k*B + (1-k)*L, where k is the final RGB transmission weight. No second fog pass follows translucency.

LOD coverage ABI

The GPU-driven cull selects the LOD level and signed coverage per view and writes them into the visible item vec4<u32>: x is the GpuScene instance row, y the scene material row, z the skin palette base (0 for rigid), and w the bitcast signed coverage (zero keeps all samples). Scene-index vertex stages resolve world, previous-world, temporal flags and probe identity through forgeax_view::common::sceneIndexDraw(visible.x); there is no per-batch Mesh row. Standard's flat material-address varying carries coverage in its fourth lane, and ShadowCaster carries it beside the material row. Forward, Deferred, rigid LOD temporal data, and ShadowCaster call the shared forgeax_view::common::applyLodCoverage predicate before committing fragments. The pair uses the same stable pixel-space noise and opposite sides of one threshold. Encoding the pair as +t / -t keeps that threshold identical after float32 upload, including near rounding boundaries. This keeps opaque depth writes and material alpha masks intact.

[!IMPORTANT] Migration: a cooked scene-index material must not read Mesh rows or treat visible.x as a batch-local index. visible.x is the scene instance row; call sceneIndexDraw(visible.x) and take coverage from bitcast<f32>(visible.w).

MaterialAsset 唯一成功路径

paramSchema -> derive -> compile/reflect -> cook/load -> extract/record 贯穿 shader 与 material。WGSL producer 只声明与 schema 对应的字段;纹理槽 携带 coordinateSet、transform 与 physicalUvScale,cook 后由 layoutIdentity 绑定 shader artifact。identity 失效时修 source 或 cook 输入, 再执行 recook/load。

[!IMPORTANT] Runtime 只查找已发布的 content-addressed artifact;恢复沿 producer、cook 与 catalog 的 owner 边界进行,不在 app 侧复制 shader artifact。

Standard Surface contract

Standard material 的作者只需要通过 #import 使用 surface_v1.wgsl,并实现唯一的 evaluate_surface(SurfaceInput) -> SurfaceData。SurfaceData 是 Standard BRDF 的输入,不是最终颜色;Engine-owned Standard pass family 负责顶点、光照、 阴影、Forward/Deferred、G-buffer、雾和输出。内建默认值位于 default_standard_surface.wgsl。

Minimal authoring shape

SurfaceInput exposes uv0 through uv7, matching the geometry coordinate inputs. Every texture slot selects its own set, scale, rotation and offset. The default Surface evaluator is shared by CPU, GPU scene-index and shadow passes; no shadow-only sampling formula may discard those authored coordinates. Changes to the engine-owned input ABI require re-cooking authored materials.

SurfaceData is deliberately base-only. Its frozen field order is:

| Order | Field | Meaning | |:--:|:--|:--| | 1 | baseColor: vec3<f32> | Base reflectance color | | 2 | normalWS: vec3<f32> | World-space shading normal | | 3 | metallic: f32 | Metallic factor | | 4 | roughness: f32 | Roughness factor | | 5 | emissive: vec3<f32> | Emissive contribution | | 6 | occlusion: f32 | Ambient occlusion factor | | 7 | opacity: f32 | Surface opacity | | 8 | alphaClipThreshold: f32 | Alpha-test threshold |

#import forgeax_material::surface_v1::{SurfaceInput, SurfaceData}

fn evaluate_surface(input: SurfaceInput) -> SurfaceData {
  return SurfaceData(
    vec3<f32>(0.45, 0.18, 0.06), input.vertexNormalWS, 0.8, 0.42,
    vec3<f32>(0.0), 1.0, 1.0, 0.5,
  );
}

The matching TypeScript entry is also import-first: the caller names the compiled Surface module and supplies only its parameters and values. The Engine still owns stage entry points, bindings, BRDF composition, and pass projection.

import { Materials } from '@forgeax/engine-render';

const rustedIron = Materials.standard({
  surfaceModule: 'game_3d::rusted_iron_surface',
  parameters: [
    { name: 'ironColor', type: 'color' },
    { name: 'rustDark', type: 'color' },
    { name: 'rustBright', type: 'color' },
    { name: 'noiseScale', type: 'f32', default: 1.85 },
  ],
  values: {
    ironColor: [0.4, 0.45, 0.47, 1],
    rustDark: [0.42, 0.085, 0.018, 1],
    rustBright: [0.95, 0.34, 0.055, 1],
    noiseScale: 1.85,
  },
});

For the complete source-to-runtime example, see the game-3d rusted-iron fixture.

[!CAUTION] Surface is a base-facts function only. Do not add clearcoat, clearcoatRoughness, other physical-layer fields, stage entries, resource bindings, Engine entry points, or vertex-position mutation. Physical layers and pass admission remain Engine-owned.

渐进导航:Materials.standard() → moduleSlots.surface → build-time #import composition / reflection → Pack cook → runtime GUID readiness。 Surface source 不声明 stage entry、@group/@binding 或 vertex mutation;参数 资源由同一 MaterialAsset.parameters contract 派生。编译或 cook 失败时读取 code、detail、hint,修复 authored source 或 producer 后重新 cook,不在 runtime 读取 raw WGSL 或创建 app-local artifact。

[!IMPORTANT] A custom material starts as WGSL source plus one MaterialAsset contract. The build manifest publishes the composed module and the material cook publishes the resolved record, artifact bytes, references, and receipt. Runtime resolves those facts from the catalog; application code does not install or duplicate shader artifacts. The recovery route is always source or cook repair.

Direct material program contract

A cooked MaterialProgramAbi always names its direct vertex entry. Its optional sceneIndexEntry exists only when the producer compiled that entry. Direct-only Unlit programs publish their actual schema-aligned row, reflected vertex inputs and skin palette address. Cook publishes plain/color and mesh/skinned programs under the same GUID; Render selects the exact resident geometry contract. Their absence of a scene entry keeps these programs on the ordinary submission lane.

Scene-index material program contract

An authored full-custom program may opt into the indirect lane only through the compiler-controlled scene-index contract. Its WGSL must expose both vs_main and vs_scene_index, import forgeax_material::parameters (or declare an equivalent producer-owned row), and use the visible-item/material index in the scene entry. The compiler validates the selected entries and publishes one MaterialProgramAbi containing the direct/scene-index entries, reflected resources, vertex inputs, and schema-derived row fields. Custom rows are packed into the canonical Standard GPU Scene row; schemas larger than that page fail at cook time and stay on their authored/specialized lane.

Render requests address: 'scene-index' for an indirect draw and address: 'direct' for the ordinary path. The runtime never reconstructs a receipt from a shader name and never silently substitutes the direct entry when the scene-index pair is absent. Engine Standard passes sharing one Surface automatically provide that opacity (including Alpha Mask) to their default ShadowCaster. The compiler publishes its scene-index ABI while preserving authored pass membership, winding and culling; a program without a shadow pass stays shadowless. An explicit shadow implementation remains authoritative. Full-custom materials can select forgeax::default-shadow-caster for native opaque coverage. The compiler publishes its direct/scene-index pair from the custom root schema and limits its vertex inputs to position plus any selected skin attributes; clipping and LOD coverage remain native. Custom deformation or cutout coverage requires an authored shadow program or explicit Surface; the compiler never infers those semantics. ShadowCaster is selected independently, so a custom shadow program may own different coverage while sharing the same material schema and resource provenance.

For an AI inspection, resolve the published artifact and branch on the structured result before preparing a draw. The renderer-facing adapter selects the concrete pass/address variant internally; the contract is the selected artifact and receipt being one producer publication:

const published = shaderRegistry.findMaterialArtifact(materialShaderId);
if (!published.ok) {
  report(published.error.code, published.error.detail, published.error.hint);
  return;
}
const receipt = published.value.receipt;
if (receipt === undefined) {
  report('material-receipt-missing');
  return;
}

Application code must not fabricate a receipt from a shader identifier. A direct-only custom artifact, including the hello/custom-shader fixture, remains valid for the ordinary path but is intentionally absent from GPU-driven admission.

Standard lit shader contract

standard-gbuffer.wgsl owns packed material encoding and decoding for rigid, skinned and custom Standard Surfaces. The deferred attachment contract lives in Render: geometry initializes the existing HDR SceneColor with emissive/opacity and writes four integer material targets. Deferred lighting adds RGB while preserving alpha. SSAO and SSR decode the shared packed normal before spatial reconstruction.

The Standard lit and skinned PBR shaders consume one forgeax_standard::cluster::evaluateStandardClusterLights accessor. The accessor decodes the shared DirectLightSlot and delegates punctual BRDF evaluation; Forward and Deferred differ only in graph topology. Unlit and fog consumers do not import this accessor. The canonical source is src/standard-cluster.wgsl; all Standard lit variants compose this one module, so there is no second implementation to maintain.

src/standard-surface.wgsl owns the Standard material/fragment evaluation shared by ordinary Mesh and the VFX Mesh vertex adapter. Geometry adapters supply world position, normal, tangent, UVs and color; they do not fork BRDF, shadow reception or IBL formulas. Render supplies the corresponding lighting resources. The single-level BRDF LUT uses explicit-LOD sampling, including when per-particle surface factors make fragment control flow non-uniform.

Geometric specular anti-aliasing is part of that shared evaluation, not a material parameter or render setting. src/specular-aa.wgsl (forgeax_pbr::specular_aa) widens GGX roughness by the pixel's interpolated-normal spread: the three.js getGeometryRoughness measure at half weight (UE NormalCurvatureToRoughness and Tokuyoshi-Kaplanyan filter the same screen derivatives). Forward direct and IBL lighting, clearcoat, and the roughness packed into the Deferred G-buffer all consume the filtered value, so both render paths agree. The spread is sampled at the top of each fragment entry because derivatives need uniform control flow. Flat surfaces are unchanged bit for bit. packages/runtime/src/__tests__/specular-aa.dawn.test.ts gates the effect against an 8x supersampled truth rendered without the filter. It also replays the captured Deferred G-buffer on a fresh device with and without the filter and records the GPU pass cost of each path.

Specular IBL conserves rough-metal energy with the table-free multiple-scattering term of Fdez-Aguera (JCGT 2019), the same form as three.js computeMultiscattering and UE EnvBRDF energy compensation. The existing split-sum LUT supplies A/B; specularEnvironmentAlbedo in src/ibl-sampling.wgsl returns FssEss + Fms·Ems with Ems = 1 − A − B and Favg = F0 + (1 − F0)/21. Every consumer (Standard Surface, Deferred lighting, clearcoat, single-layer media, skinned PBR) reaches it through projectSpecularRadiance; diffuse kD is unchanged. A white metal in a uniform radiance-one furnace returns one at every roughness, while colored metals gain only the missing bounce share. Two Dawn gates pin this: packages/render/src/__tests__/ibl-multiscatter-furnace.dawn.test.ts probes the kernel against the baked LUT, and packages/runtime/src/__tests__/ibl-furnace.dawn.test.ts renders roughness 0→1 spheres on both paths. It replays the captured shading draw with and without the term, and records interleaved GPU pass cost. A single-scatter build of the same manifest is the falsifier.

MaterialAsset and shader route

The recovery route is to inspect the structured code, detail, and hint, then repair the source or cook input before retrying the catalog load.

Declare passes[].program.module, parameters, values, and optional parent on the same MaterialAsset. A texture value keeps its own coordinates.set and coordinates.transform, so every slot remains explicit from glTF import to fragment sampling. The root contract is inherited by child materials; a child only supplies values it owns.

assets.configurePackIndex('/pack-index.json');
const result = await assets.loadByGuid<MaterialAsset>(materialGuid);
if (!result.ok) {
  report(result.error.code, result.error.detail, result.error.hint);
  return;
}
const materialHandle = world.allocSharedRef('MaterialAsset', result.value);

The custom-shader demo is the executable reference: apps/hello/custom-shader. It loads the root and derived GUIDs, validates their cooked records, checks the manifest artifact, and only then draws.

GPU-driven PBR / shadow / skin navigation

This package is the producer entry: author paramSchema and WGSL, then inspect the cooked receipt before the render owner admits a draw. The consumer route is packages/render/README.md; host assembly and recovery are documented in packages/runtime/README.md. An Alpha Mask cutoff, texture/sampler slot, or skin palette mismatch is a structured producer failure, not a CPU duplicate-draw request.

Offline recovery is always inspect -> repair this producer -> retry:

shader source/schema -> cook + reflection receipt -> runtime catalog -> render prepare

Shader module catalog

ShaderRegistry owns the content-addressed build manifest. It is a runtime lookup boundary, not an authoring store.

| Entry | Shape | Description | |:--|:--|:--| | ShaderRegistry.loadManifest() | () => Promise<Result<void, ShaderError>> | Load and validate the manifest | | ShaderRegistry.forkForDevice(device) | (ShaderRegistryDevice) => ShaderRegistry | Fork validated CPU manifest inputs into an independent registry with an empty GPU cache; device-specific material variants are prepared on the candidate | | ShaderRegistry.get(hash) | (string) => Result<ShaderModule, RhiError \| ShaderError> | Resolve an engine module by content hash | | ShaderRegistry.entries() | () => IterableIterator<ManifestEntry> | Enumerate published content-addressed modules in manifest order | | ShaderRegistry.materialShaderManifestEntries() | () => IterableIterator<MaterialShaderManifestEntry> | Enumerate validated material shader manifest rows | | ShaderRegistry.findMaterialArtifact(id) | (string) => Result<MaterialArtifact, ShaderError> | Find the published module selected by a cooked material | | ShaderRegistry.materialShaderIdentifiers() | () => IterableIterator<string> | Enumerate published material module identifiers |

loadManifest() validates the complete document before publication. A malformed entry or material shader row returns one manifest-malformed error and leaves entries(), materialShaderManifestEntries(), and lazy get() resolution unchanged. Repair the same source and retry on the same registry; a successful load publishes rows in source order and a later call is idempotent.

Device recovery uses forkForDevice to retain the admitted, validated CPU manifest without another network request or JSON parse. The candidate has independent registration maps and an empty module cache; candidate preparation selects the material shader variant for the replacement device. After prepare, the recovery transaction carries over user-installed artifacts and registrations that were admitted while the candidate was being built. A shader-preparation failure or timeout never reaches that carryover; later candidate failures discard the candidate without publication, and it cannot return a lost-device handle. An unloaded or failed source registry stays unloaded in its fork, so the original validation and source-failure behavior still applies.

The WGSL-level module and the RHI GPU handle are different concepts. This package owns the former; @forgeax/engine-rhi owns the latter.

Contract derivation

The compiler derives binding layout, uniform offsets, texture field names, and the injection boundary from the material parameter contract. WGSL reflection must agree with that derived shape before a record is published. Per-slot texture coordinates are data in MaterialTextureValue, not a global shader switch.

Standard texture availability comes from authored parameter declarations, not GPU residency. Cooked materials omit unavailable texture declarations and sampling code. Shared built-in Standard modules retain a fixed binding layout and specialize sampling with one pipeline override, STANDARD_TEXTURE_MASK_OVERRIDE. standardTextureMask() derives its bits from the canonical texture-field order; the renderer carries the authored mask separately from the canonical UBO schema and includes it in pipeline identity. Runtime selects published WGSL and GPU pipeline constants; it does not compose or cook WGSL. A declared but nonresident texture remains enabled and follows the existing resource recovery route. The override's low 20 bits retain presence; the higher bits select an earlier sample for each independent metallic, roughness, or alpha input when texture, sampler, and coordinates match. The selector is zero when that input needs its own sample. This changes sampling work without changing the Standard binding layout or the authored texture values. Bits 29 and 30 (STANDARD_TRIPLANAR_PROJECTION_BIT, STANDARD_OBJECT_SPACE_NORMAL_BIT) are projection selectors that standardProjectionMask() derives from the effective triplanarSpace and normalMapSpace values. The WGSL reads them through standardUsesTriplanarProjection() / standardUsesObjectSpaceNormal(), so every texture sample stays in uniform control flow; a per-row value never gates a sample. The ray entry is unspecialized, which is why ray admission refuses those values.

Error recovery

Material and shader errors are closed. Switch on error.code, then use the code-specific detail and hint:

| Code | Meaning | Recovery | |:--|:--|:--| | material-contract-program-mismatch | A pass does not satisfy the root contract | Align the pass module and re-cook | | material-reflection-binding-mismatch | Reflection differs from the derived bindings | Fix WGSL or parameters, then re-cook | | shader-module-not-found | The published module is absent | Add it to the build source catalog and rebuild | | material-specialization-not-cooked | No runtime artifact exists for the selection | Run the cook path and publish its record | | material-specialization-stale-generation | A dependency changed after cooking | Wait for dependencies to settle and re-cook | | material-surface-slot-missing | Standard pass has no surface module slot | Add the slot to the authored pass and re-cook | | material-surface-abi-mismatch | Surface export does not match the ABI required by the selected material model | Repair the producer-reported expected signature and re-cook; Standard uses surface_v1, while single-layer medium uses single_layer_medium_surface_v1 | | material-surface-forbidden-interface | Surface declares a stage, resource, or vertex mutation | Remove the forbidden interface and re-cook | | material-physical-contract-invalid | A root physical layer is incomplete or unsupported | Repair the root parameter fragment before composing or cooking |

Single-layer medium frame resources use the same declared shader ABI at 1x and 4x. Render resolves 4x opaque and nearest-water inputs by selecting the nearest depth sample and the color from that exact sample. The composed medium shader therefore reads two single-sample r32float depth facts; it does not read a multisampled depth attachment or an averaged color resolve. Unsupported sample counts fail at RenderGraph admission before this shader is recorded.

At an undisplaced refraction coordinate, the medium shader uses textureLoad for the paired color texel. A neighborhood filter there would average across a thin foreground or shoreline and undo the color/depth association. A displaced rough-refraction footprint may average only after the complete color/depth neighborhood passes the shared validity test. The executable 1x/4x linear-HDR edge oracle and its strict direct/GPU receipt path are documented in the Render single-layer medium contract.

Never hide one of these errors by creating an app-local artifact or changing a demo's material shape.

For the canonical error detail and executable recovery fields, use material/errors.ts. The producer recovery path is inspect -> repair authored source -> cold-cook -> verify publication; runtime does not reinterpret a Surface error.

Temporal-v1 accessor

forgeax_scene_temporal is the single WGSL accessor ABI for the forgeax::scene-data::temporal-v1 sampled data. Standard PBR producers pack the shared SceneTemporalV1 record, and TAA resolve samples it through the same accessors. Consumers must not add a private velocity or G-buffer unpack, or import compiler, Naga, or backend policy into runtime shader code.

The public render route supplies the semantic target; this package supplies the stable shader module identifier SCENE_DATA_TEMPORAL_V1_SHADER_MODULE. Layout, clear values, invalid-depth handling, and reactive fallback remain owned by the shared accessor source.

Portable Motion Blur

motion-blur.wgsl is the sole built-in raster Motion Blur producer. It samples the shared forgeax_scene_temporal accessor, derives a target-rate-scaled and clamped motion vector, probes a fixed eight-neighbour candidate set, and then performs a depth-aware symmetric gather of at most 16 color taps while preserving alpha. Invalid interval, reactive, reset, subpixel, and depth-rejected samples return the current color; the shader never reads or writes a private velocity buffer, G-buffer, storage texture, or TAA history. Runtime code selects the pass through the typed render plan; shader lookup remains a published content-addressed artifact and never imports compiler, Naga, or backend policy.

Points and Lines shader contract

Points and Lines use the engine-owned forgeax::points-lines material shader manifest row. The row is a content-addressed runtime artifact selected by the same MaterialAsset and Materials.unlit route as other built-in materials. The Standard renderer binds the Points/Lines view at group 0, binding 10, then uses the prepared expansion geometry in the active main geometry pass.

The runtime contract is compiler-free: it loads a published artifact and never imports Naga, a shader compiler, or WGSL authoring helpers. paramSchema remains the single source for derived layout and uniform shape. Manifest validation is atomic; a malformed or stale row leaves the prior published catalog unchanged and returns a structured error.

| Evidence | Meaning | |:--|:--| | shader manifest row | forgeax::points-lines is published and content-addressed | | runtime isolation triple | no Naga in dist, no runtime shader compiler dependency, and no compiler import | | graph contract | points-lines uses the existing Standard main pass and material binding path | | lane contract | direct WebGPU is the focused runtime route; clustered unlit and WebGL2 restrictions are structural contracts unless a lane-specific runtime probe is available |

When lookup or reflection fails, inspect .code, .expected, .hint, and the code-specific .detail; repair the source or cook input and republish through the build-time producer. Do not create an app-local WGSL module, bypass the manifest, or add a backend-specific shader branch. The same retained/prepared state supplies render inspection and recovery evidence.

Built-in PBR contract

The built-in PBR shader has named coordinate records for base color, metallic roughness, normal, specular weight/color, emissive, occlusion, transmission, and the declared physical texture slots. Each record carries offset, scale, rotation, coordinate set, and physical extent metadata. The render package projects the records into the UBO; the shader selects the declared vertex coordinate input per slot. specular/specularColor replace the removed The former specular-tint vocabulary was removed in one cut; no compatibility alias is supported.

Physical layer declarations are lowered from the Standard root contract. The compiler appends only authored physical resources after the engine-owned IBL and transmission region, so a base-only root has no physical binding or sample and retains its Deferred pass. The five glTF extensions use the same schema and the same Forward physical evaluator; runtime never compiles or patches this layout.

The diffuse-transmission layer (DIFFUSE_TRANSMISSION_AVAILABLE) adds a back-hemisphere Lambert lobe to every Standard light evaluator and samples irradiance along -N. The factor splits energy rather than adding it: the front diffuse albedo is scaled by 1 - factor before the transmitted albedo factor · (1 - metallic) · tint is formed (render README §Diffuse transmission). The irradiance cube has one mip, so sampleIblDiffuse uses an explicit level 0, which keeps the lookup legal inside the non-uniform frontFacing-dependent transmission branch.

The IBL region has six bindings: irradiance texture/sampler, prefilter texture/sampler, BRDF lookup texture, and skylight uniform. BRDF lookup uses the irradiance linear-clamp sampler; the separate prefilter sampler permits per-draw reflection-probe selection. Re-cook authored Standard roots after a binding-contract change so runtime and shader layouts agree.

References

Surface authoring checklist

  • [x] Import forgeax_material::surface_v1 and return the eight-field SurfaceData.
  • [x] Read generated material parameters through forgeax_material::parameters.
  • [x] Keep vertex, light, BRDF, pass, binding, and output ownership in Engine modules.
  • [x] Let the root parameter contract determine physical layers and pass admission.
  • [ ] Add an engine entry point or resource binding to a Surface module.

The final unchecked item is intentionally forbidden. If an effect needs its own entry points or render targets, declare an explicit full-custom material and publish its pass/lane provenance instead of disguising it as a Standard Surface.

Metadata-only source catalogs

new ShaderRegistry({ manifestUrl }) loads manifest and material metadata without acquiring a GPU device. GPU module lookup returns rhi-not-available until a device-backed registry is used. Render Worker source asset loading uses this catalog; the receiver installs immutable artifacts into its existing device-backed registry.

Standard deferred lighting contract

Standard and custom base Surface programs exchange SurfaceData with the Engine's rigid or skinned material entry. standard-gbuffer.wgsl encodes that result once; standard-deferred-lighting.wgsl resolves its lighting without calling Surface or replaying receiver geometry. Alpha clipping happens before any G-buffer output. Physical/medium Surface admission continues to select its explicit Forward lane when the base deferred schema cannot represent its lobes.

The shared base BRDF/IBL callsites live in standard-lighting.wgsl. Its helpers accept textures and material facts, so Forward and Deferred share light semantics without sharing a material bind-group layout. The cooked utility manifest entry is forgeax::engine-standard-deferred-lighting; it is an Engine utility, not a runtime material publication. Clustered and unclustered sources are prewarmed with the same device capability axes as Standard.

See the Render G-buffer contract for attachment formats, packing, resource limits and SSR outputs.

Shared clipping coverage

forgeax_clipping::planes owns clippedByPlanes, applyLocalClipping, and applyViewClipping. World-space negative signed distance is discarded. View coefficients append to the shared View ABI at byte 1024; its payload is 1136 bytes in a 1280-byte aligned slot. Color, shadow and temporal geometry must use the same transformed world position. Material presence is derived from the root parameter declaration during cook; runtime does not add feature defines.

Hashed alpha coverage

alpha-hash.wgsl owns the derivative-scaled object-space threshold shared by Standard Surface, Unlit and temporal coverage. alphaHash is a default-zero f32 material value (Materials projects its boolean option to 0/1). Its Standard schema slot occupies the alignment gap before specularColor, so existing subsequent uniform offsets remain unchanged. The shader producer includes the helper in the source closure; runtime never cooks this source. Sparse authored Standard contracts derive ALPHA_HASH_AVAILABLE from the alphaHash declaration, so omitted fields produce disabled coverage without adding hidden parameters.

See Alpha Hash authoring and verification.

Standard scalar map ABI

The default Surface evaluates independent metallic, roughness and alpha maps with per-slot UV data and R/G/B/A selectors. Its contract is documented in Render. The canonical schema generates scalar layout, coordinate records and texture-presence bits; Forward, Deferred, skinned and ShadowCaster share the Surface implementation.

Physical-map bindings start at STANDARD_PHYSICAL_BINDING_START, after the reserved scene-material and global-prefilter slots. Compiler lowering and renderer BGL/BG assembly consume that same constant, keeping expanded base-map and IBL bindings disjoint. Re-cook materials after this ABI change.

Single-layer medium planar reflection

The medium template keeps the empty authored parameter prefix and renderer-owned IBL/backdrop/depth injections. Bindings 15 and 16 carry the linear 2D planar reflection and a 96-byte uniform (capture view-projection, normalized world plane, enabled flag). No capture uses the existing environment term. Valid matching plane samples replace only that reflection term, leaving Fresnel, foam and transmission integration unchanged. Producer and sampler run through Render's existing target/frame lifecycle; the shader owns no capture scheduling.

Standard displacement contract

standard-displacement.wgsl owns the normalized-height vertex formula and triangle normal reconstruction. Standard rigid/skin, temporal and shadow entries call the same level-zero sampling kernel. The existing Standard parameter schema owns displacementTexture, displacementScale and displacementBias; texture coordinates remain per-slot. Material specialization constants apply to both vertex and fragment stages.

See Standard vertex displacement for authoring, density and bounds behavior. Pack cooking validates both rigid and skinned color/shadow programs in shader-compiler/src/material/__tests__/standard-displacement.integration.test.ts.

Ray Surface reference context

The ordinary Engine manifest publishes forgeax_ray::query, forgeax_ray::path_tracer and forgeax_ray::raster_source utility programs. Query and transport share ray-traversal.wgsl, including their triangle/node storage bindings. The build owns composition and reflection; runtime consumes published WGSL and the material artifacts below. These programs alone do not install a per-frame GI pass in Renderer.

The Standard SurfaceInput ABI includes uvFootprint0/1, eight conservative UV diameters. Raster adapters supply zero and continue to use implicit derivatives; the build-only ray context selects explicit mip levels from the hit's ray-cone footprint. Custom Surface modules can consume the same input and shared sampling helper. The reference admits opaque and MASK coverage; shared Standard opacity <= alphaClipThreshold rejects a candidate only when the threshold is positive. Surface status 3 means rejected coverage, while status 2 means invalid output. geometricNormalWS describes triangle orientation; vertexNormalWS is the interpolated normal used by Standard and custom Surface shading. Authored normal maps use that vertex/tangent frame. Raster adapters derive geometry from position derivatives; exact hits and cards derive it from the indexed triangle, correcting mirrored winding.

RaySurfaceProgram carries build-produced WGSL, the derived parameter schema and the resolved asset contract. admitRayMaterial(asset, material, context) shares build/runtime admission, defaulting to ray-hit. card-capture additionally requires the canonical Standard Surface; its restriction does not narrow ray qualification. The ordinary material product publishes both eligible contexts; runtime selects immutable artifacts without importing a compiler. The Standard direct-light BRDF and opaque ray BSDF share one evaluator; sampling reports the full cosine/GGX-NDF mixture density and explicit null events, including finite mapped/smoothed normals across the geometric hemisphere. Surface admission validates values; BSDF value/PDF own directional hemisphere clipping. Existing Standard energy conventions remain unchanged.

Direct material parameter regions

A producer receipt records the actual aligned parameter region. A direct program with no parameter fields has materialRow: { byteLength: 0, fields: [] }; this records the absence of a uniform region and does not allocate a placeholder row. A scene-index program requires a positive aligned GPU Scene row. The shared ABI validator rejects a zero-byte row with fields or a scene-index entry. Every cooked raster selection carries its reflected direct entry, geometry inputs and ABI, including custom programs combined with the Engine default shadow wrapper.

Surface lighting-channel transport

Standard rigid/skin and Deferred share common.wgsl::lightingChannelsMatch. evaluateStandardDirect and evaluateStandardClusterLights accept the receiver u32 mask explicitly. View.lightingChannels occupies the directional padding word (byte 76). DirectLightSlot.channels.x is the surface mask in row 5 of the 96-byte local-light ABI; volume consumers preserve this stride and do not apply surface policy. Mesh.surface is an integer tail: x/y carry visible-surface row/count, z carries receiver channels. Scene-index variants read the same GpuScenePrimitive.lightingChannels and transport it flat to fragments. GBufferOutput.receiver_geometry is vec2<u32> (packed normal/family, mask). Do not transport masks through UNORM encoding or numerical f32 conversion. See the Render channel contract for author validation, support and shadow/GI boundaries.