# `materialNodeFromSpec`

> Contract 2.0.0 · package sha256 `a857eca95b35921109faf997094bb4192f12527d9d0fcd4be7e97f06425bad6e` · generated from `configurator-runtime.d.ts` by `services/onsite-scripter/scripts/build-contract-docs.mjs`; edit the source, not this file.

Function · `import { materialNodeFromSpec } from '@holostep-configurators/runtime-client';` · stable · since 2.0.0 · section 22. Pure helpers (root of the package; run in Node and the fake)

MaterialSpec → stored MaterialNode fields, applied over `base` (envelope, prices and `opt…` URLs
kept). A map whose `url` differs from `base`'s, without an `optimizedUrl`, drops that channel's
`opt…TextureUrl`: the old twin shows the old image. Fields it can't store are listed in
`unstorable`, never dropped silently; a scalar equal to what an active map implies (section 8.2,
rule 1) is stored by that map, not listed. A gated value equal to 1.x's default (rule 8) is not
written when `base` lacks it, and an `ior` under a specular gate the result doesn't store as
true is listed, not written. Sync, pure.

**Default:** options.base undefined (a new node's fields only)

## Declaration

```ts
export declare function materialNodeFromSpec(spec: MaterialSpec, options?: {
  base?: IMaterialNode;
}): {
  node: Partial<IMaterialNode>;
  unstorable: readonly string[];
};
```

## Generic-API test (spec 3.1)

| Rule | Result |
| --- | --- |
| 1. Neutral name | pass: `materialNodeFromSpec` names no product |
| 2. A precedent | not recorded: no ledger row (coverage.json) names this member or its owner |
| 3. Two apps, or labs | not recorded: no ledger row names this member or its owner |
| 4. The cheaper fix first | not recorded per member (Appendix B decides it per need) |
