# `TextureSpec`

> 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.

Interface · `import type { TextureSpec } from '@holostep-configurators/runtime-client/contract';` · stable · since 2.0.0 · section 8. Materials and textures

At most one of [`url`](TextureSpec.url.md) and [`image`](TextureSpec.image.md). With neither, inside a partial MaterialSpec, the spec changes
only placement and sampling ([`uv`](TextureSpec.uv.md), [`scale`](TextureSpec.scale.md), [`offset`](TextureSpec.offset.md), [`rotationDeg`](TextureSpec.rotationDeg.md), [`sizeM`](TextureSpec.sizeM.md), [`wrap`](TextureSpec.wrap.md), [`sampling`](TextureSpec.sampling.md))
of the texture the slot shows now, whether it comes from the GLB, the scene model or an earlier
rule. It never pins the image, so a later [`select()`](Writer.select.md) or undo changes the image and keeps your
placement. On a slot that shows no texture it does nothing.
With [`url`](TextureSpec.url.md) or [`image`](TextureSpec.image.md) it is a new texture, placed by its own fields: every field it omits
([`uv`](TextureSpec.uv.md), [`scale`](TextureSpec.scale.md), [`offset`](TextureSpec.offset.md), [`rotationDeg`](TextureSpec.rotationDeg.md), [`sizeM`](TextureSpec.sizeM.md), [`wrap`](TextureSpec.wrap.md), [`sampling`](TextureSpec.sampling.md), [`colorSpace`](TextureSpec.colorSpace.md), [`enabled`](TextureSpec.enabled.md))
takes its default below, never the value of the texture it covers (its `texCoord`,
KHR_texture_transform or a stored `…TextureEnabled: false`), and only a URL-less spec above it
lays fields over it, while the GLB's own image keeps its authored placement. Successive writes to one rule merge first (spec section 6.10, R10), so a [`scale`](TextureSpec.scale.md)
an earlier write to that rule gave stays.
Every texture, [`url`](TextureSpec.url.md), [`optimizedUrl`](TextureSpec.optimizedUrl.md) and [`image`](TextureSpec.image.md) alike, is sampled with the glTF convention:
row 0 of the image is v = 0, never flipped.

## Members

| Member | Kind | Summary |
| --- | --- | --- |
| <a id="url"></a>[`url?`](TextureSpec.url.md) | property | png, jpg, webp, avif, ktx2, hdr. |
| <a id="image"></a>[`image?`](TextureSpec.image.md) | property | A canvas or bitmap, copied at the call. |
| <a id="optimizedurl"></a>[`optimizedUrl?`](TextureSpec.optimizedUrl.md) | property | The optimized twin the runtime prefers when present (stored `opt…TextureUrl`). |
| <a id="enabled"></a>[`enabled?`](TextureSpec.enabled.md) | property | false keeps the URL but shows no map (stored `…TextureEnabled: false`). |
| <a id="uv"></a>[`uv?`](TextureSpec.uv.md) | property | UV channel (glTF's TEXCOORD_n). |
| <a id="scale"></a>[`scale?`](TextureSpec.scale.md) | property | Repeats per UV unit (KHR_texture_transform). |
| <a id="offset"></a>[`offset?`](TextureSpec.offset.md) | property | In UV units. |
| <a id="rotationdeg"></a>[`rotationDeg?`](TextureSpec.rotationDeg.md) | property | Degrees, about the UV origin. |
| <a id="sizem"></a>[`sizeM?`](TextureSpec.sizeM.md) | property | Physical size one image covers, metres. |
| <a id="wrap"></a>[`wrap?`](TextureSpec.wrap.md) | property | One mode, or [u, v]. |
| <a id="sampling"></a>[`sampling?`](TextureSpec.sampling.md) | property | How the image is filtered when drawn smaller or larger than its pixels: 'pixel' for pixel art and data maps, a lower `anisotropy` to save GPU time on a slot seen face-on. |
| <a id="colorspace"></a>[`colorSpace?`](TextureSpec.colorSpace.md) | property | How the image's values decode. |

## Declaration

```ts
export interface TextureSpec {
  url?: string;
  image?: HTMLCanvasElement | OffscreenCanvas | ImageBitmap | ImageRef;
  optimizedUrl?: string;
  enabled?: boolean;
  uv?: 0 | 1 | 2 | 3;
  scale?: number | Point2;
  offset?: Point2;
  rotationDeg?: number;
  sizeM?: number | Point2;
  wrap?: Wrap | readonly [
    Wrap,
    Wrap
  ];
  sampling?: TextureSampling;
  colorSpace?: 'srgb' | 'linear';
}
```

## Example

Imports: values (`has`, `validate`, the `Holo*Error` classes, the pure helpers) from `@holostep-configurators/runtime-client`, types from `@holostep-configurators/runtime-client/contract`, `/math` and `/testing` names from those entries; `boot` from the runtime module the root's `loadRuntimeAtFloor` resolves to. A type your page defines is your app's: declare it there, never import it from the package.

Compiled under the contract's strict settings (`examples/declaration/08-TextureSpec.ts`):

```ts
import type { Session } from '@holostep-configurators/runtime-client/contract';
declare const session: Session;
declare const signal: AbortSignal;
declare const fabricGroup: string;
// Tile whatever fabric the shopper picks: no url, so only the placement is written.
await session.find({ slot: 'fabric' }).set({ material: { maps: {
  baseColor: { scale: 16 }, normal: { scale: 16 }, roughness: { scale: 16 },
} } });
// the image changes; the tiling stays
await session.select(fabricGroup, 'linen', { actor: 'shopper' });
```

## Generic-API test (spec 3.1)

| Rule | Result |
| --- | --- |
| 1. Neutral name | pass: `TextureSpec` names no product |
| 2. A precedent | pass: [glTF KHR_texture_transform](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform) (row `G34`) |
| 3. Two apps, or labs | pass: 4 apps (ledger: app-0c773a4ee3, app-f3598fad77; fleet cross-check: app-59c5b5751e, app-e61a0b42ad, app-f3598fad77) |
| 4. The cheaper fix first | decided per need in Appendix B: rows `G34` at tier stable (a member only where no rule, data, asset or app code serves the need) |
