forgeax-engine-material
Skill70 starsChanged 18 days agoApache-2.0View on GitHub
ForgeAX MaterialAsset authoring and visibility route. Use when creating, loading, or debugging materials, textures, lighting response, or material readiness.
In this repository25 other files it gives its agents.
- AGENTS.mdAGENTS.md
- templates/AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- forgeax-engine-appSkill
- forgeax-engine-assetsSkill
- forgeax-engine-audioSkill
- forgeax-engine-cliSkill
- forgeax-engine-debug-drawSkill
- forgeax-engine-debugSkill
- forgeax-engine-ecsSkill
- forgeax-engine-evolveSkill
- forgeax-engine-mathSkill
- forgeax-engine-modelingSkill
- forgeax-engine-navigationSkill
- forgeax-engine-performanceSkill
- forgeax-engine-physicsSkill
- forgeax-engine-render-pipelineSkill
- forgeax-engine-rhi-debugSkill
- forgeax-engine-rhiSkill
- forgeax-engine-sdkSkill
- forgeax-engine-shaderSkill
- forgeax-engine-stateSkill
- forgeax-engine-vfxSkill
- forgeax-engine-wechatSkill
- forgeax-scriptable-pack-reviewSkill
The file
We split this skill into the rules it gives an agent
12 rules. About 0 are common, 3 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 12
Use these entry points:
- Distinctive: line 20
Keep one MaterialAsset subject and one Pack publication.
- Very specific: line 57
When a visible material is used by a direct-light parity case, start with the revision-pinned three-r184-finite-range-authority.json and …
- Very specific: line 104
Use the existing Materials.standard entry with transmission, IOR, thickness, attenuation, and named texture values.
- Very specific: line 141
Verify with the back-lit falsifier pattern in apps/hello/foliage-transmission (opaque control stays dark, factor 0 must fail), its white …
- Very specific: line 212
Every other transparent draw stays in the sorted pass after the composite.
- Very specific: line 213
Read renderer.inspect().transparency.ineligible for its reason: blend-not-eligible, depth-write-enabled or program-without-oit-output.
- Very specific: line 284
Do not raise authored roughness or add a custom shader to hide sparkling highlights on curved low-roughness surfaces.
- Very specific: line 337
Use renderer.inspect and the RHI debug capture when source, derived, binding, or draw evidence is needed.
- Very specific: line 373
Read .code, .expected, .hint, and the narrowed .detail.
- Distinctive: line 403
Do not replace a missing material, mesh, camera, or visibility path with a demo-side stand-in.
- Very specific: line 416
Use renderState.colorWriteMask (RGBA bits 1/2/4/8, default 15) for per-channel writes.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
forgeax-engine-material
Contract index
Lighting asset entry points
Use these entry points:
- Declare a one-sided rectangular light with
RectAreaLight;widthandheightown its dimensions, and the optionalsourceTexture(a 2DTextureAssetof any size: rgba8/bgra8 sRGB or linear, r8 grayscale, rgba16float/rgba32float clipped to [0, 1] take the CPU projection; KTX2 BC/ETC2/ASTC block formats are resampled from level 0 on the GPU, which needs compute and the device compression feature, otherwise onefeature-not-enablederror and a white slice) tints diffuse and specular like Unreal's SourceTexture. u follows the light's local +X; an absent handle is uniform emission. Show the same image on an emitter quad yourself; the light does not draw its surface. - Bind
iesProfileonPointLight,SpotLight, orRectAreaLight. Point/Rect use Transform orientation (+Z polar axis, +X toward +Y azimuth); Rect modulates its existing LTC result by the center-to-receiver direction. Spot retainsdirection,rollDeg, and its optionalcookie(TextureAsset). IES shapes emission independently of occluder shadows; a pedestal lamp can set downward emission to zero. See IES directional emission for source format, capacity, and consumer limits. - Declare 27
irradiancevalues andradiusthroughLightProbe; GPU slots, LTC, and the probe registry are not public API.
author -> publish -> admit -> accept -> verify are evidence stages. The IES producer creates cooked IesProfileAsset at build time; runtime loads it by GUID. Follow .code/.detail/.hint back to the producer for rebuild; fallback is not verification.
Keep one MaterialAsset subject and one Pack publication. Runtime bool/value
data, composed module slots, and a closed compiler context are inputs; macros,
feature defines, runtime cooking, and app-local fallbacks are not. Trace
materialContractDigest, sourceClosureDigest, layoutIdentity,
programIdentity, cookIdentity, and materialPublicationIdentity from
current to generation, repair the first producer divergence, cold-cook the
same GUID, and verify receipt, artifact, and provenance.
[!IMPORTANT] The visible-object recipe is
MeshFilter+MeshRenderer+MaterialAsset. Author one material payload, cook its effective contract, load it by GUID, and allocate the World handle. The recovery route is structured error inspection. Do not create an app-local shader artifact or bypass an engine resource defect in a demo.
Shadow receiver bias
For a bright patch on a shaded surface, first capture the failing frame through
RHI Debug. Inspect the receiver, shadow-map depth and actual light UBO before
changing roughness, emission or shader code. Directional/Spot depthBias is a
normalized light-depth offset (default 0.00001); it is not the Point shadow's
world-meter bias. A large normalized offset grows with the cascade depth span
and can erase an overhead occluder. Start from the component defaults, then
verify both the occluded surface and a directly lit control for self-shadow acne.
See directional shadow units.
Generated Standard/Unlit casters preserve authored culling (back by default),
so their maps retain the light-facing entry depth. Exit-face-only maps can
misclassify receiver/filter samples inside a touching solid as lit. Keep
double-sided materials and explicit custom ShadowCaster state as authored;
point-face projection winding has a separate correction. Run the shared
cube-plane-shadow-contact Browser/Dawn regression for a contact-gap change: it
checks the central contact band and exposed ground/top controls in both render
paths, rather than accepting a single dark pixel or zero bias.
Color-lighting parity handoff
For a direct-light material issue, use the color-lighting parity entry, then follow its status recovery map. The SceneCase and CaseReport schemas are the report contract; repair the named material or producer owner and rerun the same case. Do not turn a missing producer capture into a material pass.
Direct-light parity entry
When a visible material is used by a direct-light parity case, start with the
revision-pinned
three-r184-finite-range-authority.json
and its executable authority test. The entry is ready only with a matching
Three revision/source hash, fixed config, and complete expected samples;
missing fields mean blocked and require evidence recovery.
The shared light vocabulary is:
| Field | Public meaning |
DirectionalLight.intensity | Lux in a one-world-unit-per-meter scene |
PointLight.intensity | Candela with positive meter range or no cutoff |
PointLight.decay | Finite nonnegative f32 distance exponent; default 2, 0 keeps only the range window, fractional values such as 1.4 are supported |
SpotLight.intensity | Candela with meter range and KHR cone mapping |
color | Linear RGB, without a global compensation factor |
cosInner / cosOuter | Snapshot fields derived from imported cone radians |
direction | Extract-normalized world direction consumed by both pipelines |
The runtime range factor is the Three r184 squared window
clamp(1 - (d / c)^4, 0, 1)^2; KHR's unsquared curve remains an import/reference
falsification and must not be used as the Forge runtime curve. Exposure belongs
to the camera tone/output stage after lighting, so material authoring and light
intensity do not receive an exposure multiplier.
Point decay reaches surface lighting, scene captures, volumes, and ray reference through the same light slot. Existing consumer near-light safety floors remain; this is not full-distance Three parity. See the Render contract for validation and attenuation details.
For diagnosis, run the authority and light-snapshot tests, inspect the
normalized snapshot and its buffer projection, then compare independent
browser WebGPU and Dawn captures. Preserve the case provenance, named
captures, raw hash, analytic/ROI metrics, and CaseReport.verdict. Do not
replace a missing engine path with a custom mesh, fallback shader, or app-local
light profile.
Optional physical features
Enable clearcoat, sheen, anisotropy, iridescence, or diffuse transmission through staticSwitches: { feature: true }
in the authored root or child, then cook and load that selection. Omitted
choices disable these layers; a scalar weight is runtime data. An explicitly
enabled zero weight keeps code available for animation. glTF import derives
these choices from the source extensions.
Transmission/refraction
Use the existing Materials.standard entry with transmission, IOR, thickness, attenuation, and named
texture values. Smooth/rough refraction, edge fallback, and transparent ordering are renderer-owned;
inspect renderer.inspect().transmission for detached capability/resource/lifecycle facts. GLTF
transmission is producer-owned and must be repaired through source -> Pack -> GUID load, including the
structured BLEND rejection path.
On a 16-sampled-texture device, transmission shares the metallicTexture / roughnessTexture /
alphaTexture slots. Authoring one of those split maps on a transmissive material there drops
refraction and reports MaterialSampledTextureBudgetExceededError (detail.conflicts names the
maps); remove them or target a 21-texture device. Check before rendering with
materialSampledTextureBudget(world, assets, handle) or the asset.material.preview result's
sampledTextureBudget. Contract: packages/render/README.md §Standard sampled-texture budget.
Diffuse transmission (foliage, paper, thin cloth)
Back-lit thin surfaces use diffuseTransmission (0..1) and diffuseTransmissionColor, with optional
diffuseTransmissionTexture (alpha, linear) and diffuseTransmissionColorTexture (RGB, sRGB), the
KHR_materials_diffuse_transmission model. It is opaque light passing through, not refraction: no
transmission, no depth-write change, no backdrop copy.
The factor is a split, not an extra term: reflected (1 - factor) · baseColor, transmitted
factor · tint, so a white leaf in a white sky stays at 1 on both sides. factor = 1 is a lossless
frosted diffuser, never glass; use transmission for see-through. Leaves sit near 0.3-0.6. Unreal
two-sided foliage adds SubsurfaceColor on top of full diffuse (about 2x in a white furnace); convert
with S = max(1, |Base| + |Sub|), factor = |Sub| / (|Base| + |Sub|),
baseColor = Base / (S · (1 - factor)), tint = Sub / (S · factor) (|c| = largest channel), which
matches Unreal's path-traced balance, and do not port its GGX back-scatter peak (render README
§Factor semantics).
- Author values with
Materials.standard({ diffuseTransmission, diffuseTransmissionColor, ... })andrenderState.cullMode: 'none'so the back face rasterizes. - Bind it to a cooked Standard alias root: module id containing
::standard-, the five canonical user-region textures, and the diffuse-transmission parameter names. The built-inforgeax::default-standard-pbrroot is layer-free, so values alone stay inert. - The layer is Forward-only; do not expect it in a Deferred GBuffer. Authored alias roots currently draw on the per-entity CPU lane and compile out clustered and rect-area lights (render README §Diffuse transmission); budget dense foliage accordingly.
- Verify with the back-lit falsifier pattern in
apps/hello/foliage-transmission(opaque control stays dark, factor 0 must fail), its white furnace (factors 0, 0.5, 1 match the opaque white control;FALSIFY=additivemust fail), and its RHI Debug browser smoke, which checks the bound pipeline's module and 68/69 bindings.
A glTF source carrying the extension imports through the same fields; out-of-range values fail as
gltf-material-physical-invalid.
Opaque Standard surface diffusion
Author ordinary Materials.standard({ baseColor, subsurfaceRadius: [9, 6, 3], subsurfaceWeight: 1 })
values in a Pack, cook and load the material by GUID, and assign it through MeshRenderer.
subsurfaceRadius is linear RGB support in presentation pixels, each finite in [0,12]; weight
is finite in [0,1]. Select the Deferred profile with subsurfaceQuality: 'high' or 'low'.
The renderer scales the support to each view's actual internal extent. Zero radius or weight
uses the ordinary path without diffusion passes or new targets.
This lane diffuses direct and existing environment diffuse while preserving specular and
emissive. It requires compute/storage textures, float32 HDR filtering/blending,
texture-formats-tier2 read-write storage and 32KiB
workgroup storage; missing capabilities fail explicitly. Its initial roster is opaque,
single-sample Standard rigid, instanced and skinned surfaces with at most 16 active quantized
RGB profiles per view. Positive demand with Forward, MSAA, blending, disabled depth writes,
single-layer medium, Diffuse GI, cube/planar captures or ray-reference programs fails rather
than silently changing the requested material. Thin diffuse transmission has its own local
backlighting semantics and is not surface diffusion.
For capture and diagnosis follow the Render contract
and the RHI Debug skill. Check the published material values, the second lighting-context
lane, the separated diffuse carrier and cs_subsurface bindings before changing author values.
The implementation acceptance record is ROI25 evidence;
an API description is not a measured quality, performance or complete-CI receipt.
Projection and non-PBR materials
| Need | Entry |
| Texture without usable UVs (rocks, terrain, kitbash) | Materials.standard({ ..., triplanar: { space: 'world' | 'object', scale, sharpness } }) |
| Baked object-space normal map | Materials.standard({ normalTexture, normalMapSpace: 'object' }) |
| Height relief on a flat polygon (trim bakes, grout, panel lines) | Materials.standard({ normalTexture, displacementTexture, displacementScale, displacementBias, displacementMode: 'parallax' }); geometry, shadows and bounds stay at the base mesh. Parallax constraints: needs normalTexture (bumpTexture ignored); no independent metallicTexture (use metallicRoughnessTexture); with transmission below the dedicated budget it falls back to normal mapping, reported in the material sampled-texture budget |
| Mirror/polished floor reflecting the scene | Pack-cooked Materials.standard({ ..., planarReflection: true }) plus Camera PlanarReflection on the same plane; leave target unset for a Renderer-owned mirror sized by resolutionScale (render README §Planar reflection capture) |
| Glossy / brushed floor with a blurred reflection | Same, with PlanarReflection.generateMipmaps: true: roughness picks the mirror mip (roughness × (levels − 1)) instead of fading it out |
| Wet ground / rain puddles (ripple-distorted reflection) | Custom Standard Surface with planarReflection: true: ripple normalWS from frameTime, per-pixel roughness for wetness; set PlanarReflection.distortion on the camera |
| Diffuse only, no highlight | Materials.lambert({ baseColor, ... }) |
| Lit look without lights (sculpt, preview) | Materials.matcap({ texture, sampler }) |
| Debug the shading normal | Materials.normal() |
space: 'object' keeps the texture on a moving mesh; 'world' keeps it in the
world. Triplanar refuses alpha masking and the slots it cannot project, and
object-space normals refuse bumpTexture, with material-authoring-contract-invalid
(detail.parameter names the option). Object-space normals also need a blue
channel: an RG-only normal texture (rg8unorm, bc5-rg-unorm, eac-rg11unorm,
Basis RG) fails readiness with asset-invalid-value
(detail.reason: 'normal-texture-lacks-blue'); keep RG normals in tangent space. Neither reaches the ray reference
(ray-material-unsupported). Matcap and Normal are unlit: no lights, no
shadow receipt. Evidence and falsifiers live in apps/hello/material-projection
(render README §Projection and the non-PBR material family).
Order-independent transparency
Transparent draws composite in back-to-front sorted order by default. For
interpenetrating or cyclic transparent layers, set the view's
Camera.transparency to TRANSPARENCY_WEIGHTED_BLENDED. Do not reorder
entities or split meshes in the app. There is no per-material opt-in.
A draw accumulates only when all of these hold:
- its blend is straight over (
src-alpha / one-minus-src-alpha) or premultiplied over (one / one-minus-src-alpha); - depth writes are off;
- it is an unskinned built-in Standard or Unlit
fs_maindraw withoutoutputs.
Every other transparent draw stays in the sorted pass after the composite.
Read renderer.inspect().transparency.ineligible for its reason:
blend-not-eligible, depth-write-enabled or program-without-oit-output.
Repair the material's render state, or accept sorted, rather than parsing
messages.
The result has exact coverage and a depth-weighted average color. It is not the exact over-operator for overlapping layers. See the OIT section.
Mental model
The recovery route is structured error inspection followed by source or cook repair; it never adds a parallel material surface.
MaterialAsset owns passes, parameters, values, and optional parent.
The root owns the effective contract. A child inherits the root and supplies
only its changed values. A texture value is structured:
{
texture: textureGuid,
sampler: samplerGuid,
coordinates: {
set: 1,
transform: { offset: [0.1, 0.2], scale: [2, 2], rotation: 0.25 },
},
}The coordinate set and transform remain attached to the named texture slot. The glTF bridge, pack cook, runtime extract, and built-in PBR shader consume that same data.
For a render target or reflection probe, the material consumes the public
RenderTargetTextureSource projection produced by the Renderer. The source is
bound to the named texture slot with its exact dimension and mip view; it is not
an asset handle or an app-local texture registry. A 3d or 2d-array target
binds to a texture_3d or texture_2d_array param (source dimension: '3d' /
'2d-array'); a material sampling a target is excluded from that target's own
capture. Probe sampling uses the
renderer-owned selected probe, roughness mip, local box projection, and
Skylight irradiance fallback. Read pixels only after the matching
FrameReceipt.completed promise resolves.
A Three VideoTexture on a mesh maps to a VideoAsset bound as the material
texture (baseColorTexture, emissive or a texture_external root param) on a
renderable that also carries VideoPlayer (playing, loop). Import the clip
with forgeax asset add assets/clip.mp4 (.mp4/.m4v/.webm/.ogv; the
container is streamed, never inlined) or create { kind: 'video', url } at
runtime. In a DOM realm call installDomVideoSourceProvider(world) from
@forgeax/engine/graphics-extras; it creates the muted inline <video> and
follows VideoPlayer. In the default Engine Worker App already installed
installWorkerVideoSourceProvider: the page owns the <video> (the Three
VideoTexture element) and transfers each new frame as a VideoFrame, one in
flight per consumer. Do not call the DOM installer there (it returns
video-dom-unavailable); read provider.status(entity, clip) for
video-autoplay-blocked / video-load-failed and call provider.resume() from
a gesture.
A caller GPUTexture or a video uses renderer.importTexture({ kind:
'gpu-texture' | 'video', ... }). Bind { source: importedHandle.source }
through assets.update(materialHandle, { values: { slot: source } });
ordinary slots copy video frames. Zero-copy video uses a root parameter of
type texture_external. Update the loaded material handle or use
assets.copy to preserve its admitted program contract; constructing a fresh
material from a detached payload does not transfer a cooked projection.
Branch on external-texture-invalid / external-texture-state-invalid
detail.reason (Render external textures).
Standard PBR always applies geometric specular anti-aliasing: roughness widens
by the screen-space normal spread in Forward, Deferred, IBL, and clearcoat.
Do not raise authored roughness or add a custom shader to hide sparkling
highlights on curved low-roughness surfaces. The contract lives in
packages/shader/README.md under Standard lit shader contract.
Specular IBL also compensates multiple scattering, so rough metal keeps its
energy: in a uniform environment a white metal matches the sky at every
roughness. Do not brighten rough metals with a higher baseColor, intensity,
or an emissive term to offset split-sum darkening; that loss no longer exists.
Author, cook, load
const material: MaterialAsset = {
kind: 'material',
parent: parentGuid,
values: { baseColor: [0.2, 0.55, 0.95, 1] },
};
assets.configurePackIndex('/pack-index.json');
const loaded = await assets.load(materialGuid, 'material');
if (!loaded.ok) {
report(loaded.error.code, loaded.error.detail, loaded.error.hint);
return;
}
const handle = loaded.value;
// Bind directly to MeshRenderer.materials; release the caller reference after binding.For a quick visible surface, a project that mounts Engine Content
(assetLibraries) can load EngineContent.materials.default — a Standard
material with a mipmapped checker base color — or any EngineContent.meshes.*
mesh, whose Default slot already defaults to that material. Author a project
material with parent set to it to override values.
For a custom module, put passes[].program.module in the root contract. The
shader build publishes the module; the material cook publishes the record and
artifact. The application only performs the catalog load and readiness check.
Points and Lines route
For first-class point or line authoring, route the request through the existing asset and material owners:
- Confirm the
MeshAssethaspoint-list,line-listorline-striptopology. - Admit exactly one
PointsorLinescomponent with finite positivesizeorwidth.Points.sizeUnits/Lines.widthUnitsselectpixels(default) orworld;Points.shapeissquare,circleorsoft;Lines.capselectsbutt(default) orroundends and joins. Points read the meshcolorstream as a per-point tint anduv1.xas a size scale. - Select
Materials.unlit; its render state supplies blending and depth writes and its base-colour texture maps across each point sprite. Do not author a replacement shader or mesh. - Let Standard extract, prepare, and record the retained expansion.
- Use
renderer.inspectand the RHI debug capture when source, derived, binding, or draw evidence is needed.
Admission is atomic. Unsupported topology, invalid style, lane conflicts, or a
missing unlit forward material return a structured refusal. Read .code,
.expected, .hint, and narrowed .detail; never infer support from a
message or silently substitute a generic mesh draw.
The direct WebGPU probe is runtime evidence. Clustered unlit, WebGL2 capability restrictions, and RhiNull structural-only records are separate evidence rows; RhiNull does not prove pixels or hardware timing. Recovery is inspect, repair the named source or producer, rebuild or cold-cook, then retry the same retained/prepared renderer owner. There is no new CLI, RPC, registry, cache, or recovery ledger for this route.
Built-in PBR slots
Built-in PBR names its coordinate records by texture slot: base color, metallic roughness, normal, specular tint, emissive, and occlusion. Each record contains offset, scale, rotation, coordinate set, and physical extent data. The vertex input selects the requested coordinate set and clamps only when the primitive has fewer sets than the material requires.
Recovery checklist
| Symptom | Inspect | Recovery |
| Black standard material | DirectionalLight, effective pass, render error | Add the required light or repair the pass, then draw again |
material-parent-not-found | detail.missingParent, detail.chain | Fix the GUID and re-cook the child |
material-circular-inheritance | detail.chain | Remove the repeated parent and re-cook |
material-value-unknown | detail.parameter | Declare the parameter in the root contract or remove the value |
material-value-type-mismatch | detail.expectedType, detail.actualType | Change the value and re-cook |
material-specialization-not-cooked | requested material and selection | Run the cook path and publish the record and artifact |
material-specialization-stale-generation | detail.dependencies | Re-cook after dependent sources settle |
gltf-material-uv-set-missing | slot and available sets | Add the source UV set and re-import |
Read .code, .expected, .hint, and the narrowed .detail; this is the
recovery route, and it never parses a diagnostic message or silently replaces
an engine resource.
Routing
- Material shape and error union:
packages/types/README.md - Pack/cook record:
packages/pack/README.md - Shader module and reflection:
packages/shader/README.md - Runtime catalog:
packages/assets-runtime/README.md - Migration:
docs/material-asset-migration.md
Visibility is a render boundary, not a material field
Quick start: author Visibility through ECS, inspect its effective state from
the render package, and leave MaterialAsset unchanged:
import { Visibility, VisibilityStateValue, resolveVisibility } from '@forgeax/engine/render';
world.spawn({ component: Visibility, data: { state: VisibilityStateValue.hidden } }).unwrap();
const snapshot = resolveVisibility(world);| Question | Authority | Recovery |
| Why is an entity hidden? | Visibility intent plus resolveVisibility | Inspect source and hierarchy diagnostics |
| Why did a material fail? | Material/shader structured errors | Repair the cooked contract and retry |
| Why is the count unexpected? | renderer.inspect().visibilityStats | Inspect the renderer candidate path |
Do not replace a missing material, mesh, camera, or visibility path with a demo-side stand-in. Visibility does not own camera, picking, lifecycle, assets, or VFX shadow behavior. Those are out of scope for this material skill.
Custom multiple outputs
passes[].outputs declares ordered named color outputs with format, optional
blend and writeMask. WGSL @location(n) selects index n. Cook validates the
selected fragment interface; load through the ordinary GUID route. Bind matching
RenderGraph targets through public MRT,
then inspect all attachments through RHI Debug rather than accepting color alone.
Color writes and coplanar overlays
Use renderState.colorWriteMask (RGBA bits 1/2/4/8, default 15) for per-channel
writes. Mask 0 with depth writes enabled makes an invisible occluder; place it
before affected opaque geometry with the existing queue. Use depthBias (signed
32-bit constant units), depthBiasSlopeScale, and optional depthBiasClamp for
polygon offset. Zero defaults need no enable flag. Negative bias pulls forward
under conventional less-depth; an overlay usually disables depth writes.
With multiple outputs, this mask intersects each output's writeMask.
Generated shadow passes retain polygon offset and culling but own depth writes; color suppression does not disable shadows. Derived temporal attachments write all scene-data channels. See the pass table and RHI Debug regression commands before interpreting masks on deferred/MRT attachments. Inspect both pipeline state and live/fresh-replay pixels, including the zero-bias and write-off controls.
Lane admission for Unlit and morph
An opaque rigid Materials.unlit draw (queue below RenderQueue.Transparent, declared program) takes the GPU-driven color and shadow lanes like Standard; skinned, blended, Transparent/Overlay queue or program-less Unlit stays on the CPU lane and keeps its direct order. A MorphWeights entity over Mesh morphTargets deforms through a renderer-owned compute stream under any material program and owns its batch; authors add no morph variant or shader code. Read the admitted lane and reason from renderer.inspect() gpuDriven rather than inferring it (render README §GPU-driven PBR / shadow / skin navigation).
Material and punctual default behavior
Standard roughness, emissiveIntensity, and sheenRoughness default to 1.
Black emissive/sheen colors still disable their contribution; physical sheen
still needs its static switch. Explicit zero intensity remains zero.
Emissive is scene-linear and passes through exposure and the tone curve: under
ACES, large emissive surfaces above about 1.5 clip toward white and lose hue;
use 0.8-1.5 for panels and windows and 3-10 only for small bloom sources
(SDK runtime-light-emissive).
Glow meshes (beams, light pillars) add light: emission-only Surface (return surfaceEmissionOnly(input, color, opacity) from forgeax_material::surface_v1: the template still lights an additive Surface, the helper zeroes sun Fresnel glare and sky reflection), one / one
colour blend, zero / one alpha, peak under the bloom threshold, renderState.fog: false to skip scene fog (any forward draw; deferred opaque still fogs), alpha fading with
height on an open tapered mesh (SDK beam-pillar).
A Surface defines material properties only; it gets no sun, sky or camera. For a sea use the stock model: 'single-layer-medium' (sun glint, Skylight environment reflection, fog; render README "Sea sun and sky").
Point and spot lights default to no distance cutoff. Use an explicit finite
range to bound cluster membership and shading cost in many-light scenes.
A no-cutoff light reaches all camera clusters, including when its source is
outside the frustum. The default spot has a 60-degree outer and 0-degree inner
half-angle (falloff across the whole cone); outerConeDeg alone narrows it.
Equal angles give a hard cone; they are valid with shadows on or off and use a defined hard step in
surface and volume shaders. Spot shadow farPlane remains its own range.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

