@forgeax/engine-shader 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +99 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/ShaderRegistry.d.ts +208 -0
  5. package/dist/ShaderRegistry.d.ts.map +1 -0
  6. package/dist/__tests__/atmosphere-background.test.d.ts +2 -0
  7. package/dist/__tests__/atmosphere-background.test.d.ts.map +1 -0
  8. package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts +2 -0
  9. package/dist/__tests__/atmosphere-cubemap-orientation.test.d.ts.map +1 -0
  10. package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts +2 -0
  11. package/dist/__tests__/atmosphere-preetham-numeric.test.d.ts.map +1 -0
  12. package/dist/__tests__/atmosphere-preetham-red.test.d.ts +2 -0
  13. package/dist/__tests__/atmosphere-preetham-red.test.d.ts.map +1 -0
  14. package/dist/__tests__/atmosphere-preetham.test.d.ts +2 -0
  15. package/dist/__tests__/atmosphere-preetham.test.d.ts.map +1 -0
  16. package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts +2 -0
  17. package/dist/__tests__/atmosphere-three-r184-reference.test.d.ts.map +1 -0
  18. package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts +2 -0
  19. package/dist/__tests__/bloom-fxaa-tonemap.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts +2 -0
  21. package/dist/__tests__/builtin-texture-sampling-contract.test.d.ts.map +1 -0
  22. package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts +2 -0
  23. package/dist/__tests__/default-standard-pbr-alpha.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/deferred-lighting-ssao.test.d.ts +2 -0
  25. package/dist/__tests__/deferred-lighting-ssao.test.d.ts.map +1 -0
  26. package/dist/__tests__/fog-oracle.unit.test.d.ts +2 -0
  27. package/dist/__tests__/fog-oracle.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts +2 -0
  29. package/dist/__tests__/fog-orthographic-ray.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/fog-producer-matrix.integration.test.d.ts +2 -0
  31. package/dist/__tests__/fog-producer-matrix.integration.test.d.ts.map +1 -0
  32. package/dist/__tests__/hdrp-ssao-compile.test.d.ts +2 -0
  33. package/dist/__tests__/hdrp-ssao-compile.test.d.ts.map +1 -0
  34. package/dist/__tests__/ibl-irradiance.unit.test.d.ts +2 -0
  35. package/dist/__tests__/ibl-irradiance.unit.test.d.ts.map +1 -0
  36. package/dist/__tests__/ibl-sampling.unit.test.d.ts +2 -0
  37. package/dist/__tests__/ibl-sampling.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/lighting-punctual.unit.test.d.ts +2 -0
  39. package/dist/__tests__/lighting-punctual.unit.test.d.ts.map +1 -0
  40. package/dist/__tests__/material-artifact-dedup.unit.test.d.ts +2 -0
  41. package/dist/__tests__/material-artifact-dedup.unit.test.d.ts.map +1 -0
  42. package/dist/__tests__/material-builtins.unit.test.d.ts +2 -0
  43. package/dist/__tests__/material-builtins.unit.test.d.ts.map +1 -0
  44. package/dist/__tests__/material-contract.unit.test.d.ts +2 -0
  45. package/dist/__tests__/material-contract.unit.test.d.ts.map +1 -0
  46. package/dist/__tests__/material-derived-builtins.integration.test.d.ts +2 -0
  47. package/dist/__tests__/material-derived-builtins.integration.test.d.ts.map +1 -0
  48. package/dist/__tests__/public-surface.unit.test.d.ts +2 -0
  49. package/dist/__tests__/public-surface.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/scene-temporal.unit.test.d.ts +2 -0
  51. package/dist/__tests__/scene-temporal.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/shader-registry-transaction.unit.test.d.ts +2 -0
  53. package/dist/__tests__/shader-registry-transaction.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/shader.unit.test.d.ts +2 -0
  55. package/dist/__tests__/shader.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/sprite-lit-shader.test.d.ts +2 -0
  57. package/dist/__tests__/sprite-lit-shader.test.d.ts.map +1 -0
  58. package/dist/__tests__/sprite-variants.unit.test.d.ts +2 -0
  59. package/dist/__tests__/sprite-variants.unit.test.d.ts.map +1 -0
  60. package/dist/__tests__/taa-resolve.unit.test.d.ts +2 -0
  61. package/dist/__tests__/taa-resolve.unit.test.d.ts.map +1 -0
  62. package/dist/__tests__/transparent-pbr.unit.test.d.ts +2 -0
  63. package/dist/__tests__/transparent-pbr.unit.test.d.ts.map +1 -0
  64. package/dist/__tests__/vertex-color-variant.unit.test.d.ts +2 -0
  65. package/dist/__tests__/vertex-color-variant.unit.test.d.ts.map +1 -0
  66. package/dist/errors.d.ts +96 -0
  67. package/dist/errors.d.ts.map +1 -0
  68. package/dist/index.d.ts +44 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.mjs +560 -0
  71. package/dist/index.mjs.map +1 -0
  72. package/dist/material/artifact-registry.d.ts +31 -0
  73. package/dist/material/artifact-registry.d.ts.map +1 -0
  74. package/dist/material/artifact-types.d.ts +13 -0
  75. package/dist/material/artifact-types.d.ts.map +1 -0
  76. package/dist/material-schemas.d.ts +8 -0
  77. package/dist/material-schemas.d.ts.map +1 -0
  78. package/dist/register-default-sprite-lit.d.ts +40 -0
  79. package/dist/register-default-sprite-lit.d.ts.map +1 -0
  80. package/dist/register-default-standard-pbr-skin.d.ts +34 -0
  81. package/dist/register-default-standard-pbr-skin.d.ts.map +1 -0
  82. package/dist/tonemap.d.ts +13 -0
  83. package/dist/tonemap.d.ts.map +1 -0
  84. package/dist/types.d.ts +47 -0
  85. package/dist/types.d.ts.map +1 -0
  86. package/package.json +58 -0
  87. package/src/ShaderRegistry.ts +519 -0
  88. package/src/__tests__/atmosphere-background.test.ts +33 -0
  89. package/src/__tests__/atmosphere-cubemap-orientation.test.ts +68 -0
  90. package/src/__tests__/atmosphere-preetham-numeric.test.ts +122 -0
  91. package/src/__tests__/atmosphere-preetham-red.test.ts +52 -0
  92. package/src/__tests__/atmosphere-preetham.test.ts +132 -0
  93. package/src/__tests__/atmosphere-three-r184-reference.test.ts +122 -0
  94. package/src/__tests__/bloom-fxaa-tonemap.unit.test.ts +31 -0
  95. package/src/__tests__/builtin-texture-sampling-contract.test.ts +56 -0
  96. package/src/__tests__/default-standard-pbr-alpha.unit.test.ts +19 -0
  97. package/src/__tests__/deferred-lighting-ssao.test.ts +114 -0
  98. package/src/__tests__/fog-oracle.unit.test.ts +132 -0
  99. package/src/__tests__/fog-orthographic-ray.unit.test.ts +62 -0
  100. package/src/__tests__/fog-producer-matrix.integration.test.ts +77 -0
  101. package/src/__tests__/hdrp-ssao-compile.test.ts +185 -0
  102. package/src/__tests__/ibl-irradiance.unit.test.ts +43 -0
  103. package/src/__tests__/ibl-sampling.unit.test.ts +40 -0
  104. package/src/__tests__/lighting-punctual.unit.test.ts +66 -0
  105. package/src/__tests__/manifest.fixture.json +22 -0
  106. package/src/__tests__/material-artifact-dedup.unit.test.ts +21 -0
  107. package/src/__tests__/material-builtins.unit.test.ts +111 -0
  108. package/src/__tests__/material-contract.unit.test.ts +79 -0
  109. package/src/__tests__/material-derived-builtins.integration.test.ts +30 -0
  110. package/src/__tests__/public-surface.unit.test.ts +9 -0
  111. package/src/__tests__/scene-temporal.unit.test.ts +32 -0
  112. package/src/__tests__/shader-registry-transaction.unit.test.ts +116 -0
  113. package/src/__tests__/shader.unit.test.ts +1909 -0
  114. package/src/__tests__/sprite-lit-shader.test.ts +240 -0
  115. package/src/__tests__/sprite-variants.unit.test.ts +209 -0
  116. package/src/__tests__/taa-resolve.unit.test.ts +32 -0
  117. package/src/__tests__/transparent-pbr.unit.test.ts +21 -0
  118. package/src/__tests__/vertex-color-variant.unit.test.ts +52 -0
  119. package/src/atmosphere-background.wgsl +80 -0
  120. package/src/atmosphere-cubemap.wgsl +72 -0
  121. package/src/atmosphere-ibl.wgsl +127 -0
  122. package/src/atmosphere-preetham.wgsl +214 -0
  123. package/src/bloom-blur.wgsl +89 -0
  124. package/src/bloom-bright.wgsl +50 -0
  125. package/src/bloom-composite.wgsl +54 -0
  126. package/src/brdf.wgsl +25 -0
  127. package/src/common.wgsl +523 -0
  128. package/src/default-standard-pbr-skin.wgsl +538 -0
  129. package/src/default-standard-pbr.wgsl +794 -0
  130. package/src/errors.ts +169 -0
  131. package/src/fog.wgsl +27 -0
  132. package/src/fxaa.wgsl +259 -0
  133. package/src/hdrp-cluster-forward.wgsl +186 -0
  134. package/src/hdrp-deferred-lighting.wgsl +69 -0
  135. package/src/hdrp-ssao.wgsl +174 -0
  136. package/src/ibl-brdf-lut.wgsl +92 -0
  137. package/src/ibl-equirect-to-cube.wgsl +56 -0
  138. package/src/ibl-irradiance.wgsl +89 -0
  139. package/src/ibl-prefilter.wgsl +101 -0
  140. package/src/ibl-sampling.wgsl +58 -0
  141. package/src/ibl-shared.wgsl +118 -0
  142. package/src/index.ts +166 -0
  143. package/src/lighting-directional.wgsl +471 -0
  144. package/src/lighting-punctual.wgsl +274 -0
  145. package/src/material/artifact-registry.ts +78 -0
  146. package/src/material/artifact-types.ts +26 -0
  147. package/src/material-schemas.ts +48 -0
  148. package/src/msdf-text.wgsl +208 -0
  149. package/src/pbr-temporal.wgsl +73 -0
  150. package/src/register-default-sprite-lit.ts +87 -0
  151. package/src/register-default-standard-pbr-skin.ts +74 -0
  152. package/src/shadow-pcf.wgsl +120 -0
  153. package/src/shadow_caster.wgsl +54 -0
  154. package/src/skybox.wgsl +75 -0
  155. package/src/sprite-lit.wgsl +307 -0
  156. package/src/sprite.wgsl +365 -0
  157. package/src/taa-resolve.wgsl +129 -0
  158. package/src/tbn.wgsl +54 -0
  159. package/src/tonemap.ts +13 -0
  160. package/src/tonemap.wgsl +177 -0
  161. package/src/types.ts +59 -0
  162. package/src/unlit.wgsl +186 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,560 @@
1
+ import { ok, err, ParamSchemaProjectionOwner, findUndeclaredSampledTextures } from '@forgeax/engine-types';
2
+ export { MATERIAL_PARAM_TYPES } from '@forgeax/engine-types';
3
+ import { ok as ok$1, err as err$1 } from '@forgeax/engine-rhi';
4
+ export { err, ok } from '@forgeax/engine-rhi';
5
+
6
+ // src/material-schemas.ts
7
+ var STANDARD_PBR_ALPHA_CUTOFF_DEFAULT = 0;
8
+ var DEFAULT_STANDARD_PBR_PARAM_SCHEMA = [
9
+ { name: "baseColor", type: "color", default: [1, 1, 1, 1] },
10
+ { name: "metallic", type: "f32", default: 0 },
11
+ { name: "roughness", type: "f32", default: 0.5 },
12
+ { name: "metallicChannel", type: "f32", default: 2 },
13
+ { name: "roughnessChannel", type: "f32", default: 1 },
14
+ { name: "aoChannel", type: "f32", default: 0 },
15
+ { name: "extraChannel", type: "f32", default: 0 },
16
+ { name: "emissive", type: "vec3", colorSpace: "srgb", default: [0, 0, 0] },
17
+ { name: "emissiveIntensity", type: "f32", default: 0 },
18
+ { name: "occlusionStrength", type: "f32", default: 1 },
19
+ { name: "alphaCutoff", type: "f32", default: STANDARD_PBR_ALPHA_CUTOFF_DEFAULT },
20
+ { name: "clearcoat", type: "f32", default: 0 },
21
+ { name: "clearcoatRoughness", type: "f32", default: 0.5 },
22
+ { name: "specularTint", type: "vec3", colorSpace: "srgb", default: [1, 1, 1] },
23
+ { name: "normalScale", type: "f32", default: 1 },
24
+ { name: "baseColorTexture", type: "texture2d" },
25
+ { name: "metallicRoughnessTexture", type: "texture2d" },
26
+ { name: "normalTexture", type: "texture2d" },
27
+ { name: "specularTintTexture", type: "texture2d" },
28
+ { name: "emissiveTexture", type: "texture2d" },
29
+ { name: "occlusionTexture", type: "texture2d" }
30
+ ];
31
+ var DEFAULT_UNLIT_PARAM_SCHEMA = [
32
+ { name: "baseColor", type: "color", default: [1, 1, 1, 1] },
33
+ { name: "alphaCutoff", type: "f32", default: 0 },
34
+ { name: "baseColorTexture", type: "texture2d" }
35
+ ];
36
+ var DEFAULT_SPRITE_PARAM_SCHEMA = [
37
+ { name: "colorTint", type: "vec4", colorSpace: "srgb", default: [1, 1, 1, 1] },
38
+ { name: "region", type: "vec4", default: [0, 0, 1, 1] },
39
+ { name: "pivotAndSize", type: "vec4", default: [0.5, 0.5, 1, 1] },
40
+ { name: "slicesAndMode", type: "vec4", default: [0, 0, 0, 0] },
41
+ { name: "baseColorTexture", type: "texture2d" }
42
+ ];
43
+ var DEFAULT_MSDF_TEXT_PARAM_SCHEMA = [
44
+ { name: "tintColor", type: "color", default: [1, 1, 1, 1] },
45
+ { name: "distanceRange", type: "vec4", default: [4, 512, 512, 0] },
46
+ { name: "baseColorTexture", type: "texture2d" }
47
+ ];
48
+ var ShaderError = class extends Error {
49
+ name = "ShaderError";
50
+ code;
51
+ expected;
52
+ hint;
53
+ lineNum;
54
+ linePos;
55
+ detail;
56
+ constructor(init) {
57
+ super(init.message);
58
+ this.code = init.code;
59
+ this.expected = init.expected;
60
+ this.hint = init.hint;
61
+ this.lineNum = init.lineNum;
62
+ this.linePos = init.linePos;
63
+ this.detail = init.detail;
64
+ }
65
+ };
66
+ function manifestMalformed(args) {
67
+ return new ShaderError({
68
+ code: "manifest-malformed",
69
+ expected: "manifest.json parses + every entry has {hash, wgsl, glsl, bindings}",
70
+ message: args.message,
71
+ hint: args.hint,
72
+ ...args.reason !== void 0 ? { detail: { reason: args.reason } } : {}
73
+ });
74
+ }
75
+ function shaderNotFound(args) {
76
+ return new ShaderError({
77
+ code: "shader-not-found",
78
+ expected: `manifest.entries contains entry with hash '${args.hash}'`,
79
+ message: `ShaderRegistry: hash '${args.hash}' not present in manifest`,
80
+ hint: args.hint
81
+ });
82
+ }
83
+ function materialShaderNotFound(args) {
84
+ return new ShaderError({
85
+ code: "material-shader-not-found",
86
+ expected: `ShaderRegistry has registered identifiers: [${args.expected.join(", ")}]`,
87
+ message: `ShaderRegistry: material shader identifier '${args.identifier}' not registered`,
88
+ hint: args.hint,
89
+ detail: { reason: `identifier=${args.identifier}`, registeredShaderIds: args.expected }
90
+ });
91
+ }
92
+ function equalBytes(left, right) {
93
+ return left.length === right.length && left.every((value, index) => value === right[index]);
94
+ }
95
+ var MaterialArtifactRegistry = class {
96
+ #artifacts = /* @__PURE__ */ new Map();
97
+ register(artifact) {
98
+ const previous = this.#artifacts.get(artifact.key);
99
+ if (previous !== void 0) {
100
+ if (equalBytes(previous.bytes, artifact.bytes)) return ok(previous);
101
+ return err({
102
+ code: "material-artifact-conflict",
103
+ expected: "one immutable artifact byte sequence per specialization key",
104
+ hint: "re-cook the conflicting specialization and publish one artifact digest",
105
+ detail: {
106
+ key: artifact.key,
107
+ ...previous.digest ? { existingDigest: previous.digest } : {},
108
+ ...artifact.digest ? { incomingDigest: artifact.digest } : {}
109
+ }
110
+ });
111
+ }
112
+ const immutable = Object.freeze(artifact);
113
+ this.#artifacts.set(artifact.key, immutable);
114
+ return ok(immutable);
115
+ }
116
+ get(key) {
117
+ return this.#artifacts.get(key);
118
+ }
119
+ inspect(key) {
120
+ const artifact = this.#artifacts.get(key);
121
+ if (artifact === void 0) return void 0;
122
+ return {
123
+ key: artifact.key,
124
+ ...artifact.digest === void 0 ? {} : { digest: artifact.digest },
125
+ byteLength: artifact.bytes.byteLength,
126
+ ...artifact.metadata === void 0 ? {} : { metadata: artifact.metadata }
127
+ };
128
+ }
129
+ };
130
+ function registerMaterialArtifact(registry, artifact) {
131
+ return registry.register(artifact);
132
+ }
133
+
134
+ // src/material/artifact-types.ts
135
+ function isMaterialShaderArtifact(value) {
136
+ if (value === null || typeof value !== "object") return false;
137
+ const artifact = value;
138
+ return typeof artifact.material === "string" && typeof artifact.pass === "string" && typeof artifact.wgsl === "string" && typeof artifact.layoutIdentity === "string" && Array.isArray(artifact.bindings) && Array.isArray(artifact.deps) && Array.isArray(artifact.vertexInputs);
139
+ }
140
+
141
+ // src/register-default-sprite-lit.ts
142
+ var RESERVED_ID = "forgeax::sprite-lit";
143
+ function registerDefaultSpriteLit(registry, composedWgsl, caps) {
144
+ registry.installMaterialArtifact(RESERVED_ID, {
145
+ source: composedWgsl,
146
+ paramSchema: DEFAULT_SPRITE_PARAM_SCHEMA
147
+ });
148
+ }
149
+
150
+ // src/register-default-standard-pbr-skin.ts
151
+ var RESERVED_ID2 = "forgeax::pbr-skin";
152
+ function registerDefaultStandardPbrSkin(registry, composedWgsl, caps) {
153
+ registry.installMaterialArtifact(RESERVED_ID2, {
154
+ source: composedWgsl,
155
+ paramSchema: DEFAULT_STANDARD_PBR_PARAM_SCHEMA
156
+ });
157
+ }
158
+ var FORGEAX_RESERVED_PATH_PREFIX = "forgeax::";
159
+ var ShaderRegistry = class {
160
+ #device;
161
+ #manifestUrl;
162
+ // hash → ManifestEntry index (populated after loadManifest).
163
+ #entries = /* @__PURE__ */ new Map();
164
+ // hash → ShaderModule cache (populated lazily on first get()).
165
+ #moduleCache = /* @__PURE__ */ new Map();
166
+ // identifier → MaterialShaderEntry index (populated by
167
+ // installMaterialArtifact; feat-20260523-shader-template-instance-split M5 / T05).
168
+ #materialShaders = /* @__PURE__ */ new Map();
169
+ #paramSchemaProjectionOwner = new ParamSchemaProjectionOwner();
170
+ #materialShaderManifestEntries = [];
171
+ #manifestLoaded = false;
172
+ constructor(opts) {
173
+ this.#device = opts.device;
174
+ this.#manifestUrl = opts.manifestUrl;
175
+ }
176
+ /**
177
+ * Loads manifest.json and populates the hash → ManifestEntry index.
178
+ *
179
+ * Failure paths (charter proposition 4: explicit failure):
180
+ * - fetch / data: URL parse failed → `Result.err(ShaderError.manifest-malformed)`
181
+ * - JSON.parse failed → ditto
182
+ * - schema missing the `entries` field / an entries element missing
183
+ * hash/wgsl/bindings → ditto
184
+ * - a material shader row is incomplete → ditto
185
+ *
186
+ * Validation is atomic: the registry does not publish any entry or
187
+ * material shader row until the complete document has been validated.
188
+ *
189
+ * Idempotent: subsequent calls reuse the first result (never re-fetch;
190
+ * charter proposition 6: idempotency).
191
+ */
192
+ async loadManifest() {
193
+ if (this.#manifestLoaded) return ok$1(void 0);
194
+ if (this.#manifestUrl === void 0) {
195
+ this.#manifestLoaded = true;
196
+ return ok$1(void 0);
197
+ }
198
+ let raw;
199
+ try {
200
+ const response = await fetch(this.#manifestUrl);
201
+ raw = await response.text();
202
+ } catch (e) {
203
+ return err$1(
204
+ manifestMalformed({
205
+ message: `ShaderRegistry: failed to fetch manifest at ${this.#manifestUrl}`,
206
+ hint: "verify manifest URL is reachable; check bundler emitFile output path",
207
+ reason: e instanceof Error ? e.message : String(e)
208
+ })
209
+ );
210
+ }
211
+ let parsed;
212
+ try {
213
+ parsed = JSON.parse(raw);
214
+ } catch (e) {
215
+ return err$1(
216
+ manifestMalformed({
217
+ message: "ShaderRegistry: manifest JSON parse failed",
218
+ hint: "manifest.json must be valid JSON; rebuild via @forgeax/engine-vite-plugin-shader generateBundle",
219
+ reason: e instanceof Error ? e.message : String(e)
220
+ })
221
+ );
222
+ }
223
+ if (typeof parsed !== "object" || parsed === null || !("entries" in parsed)) {
224
+ return err$1(
225
+ manifestMalformed({
226
+ message: "ShaderRegistry: manifest missing required `entries` field",
227
+ hint: "manifest schema requires top-level `entries: ManifestEntry[]`"
228
+ })
229
+ );
230
+ }
231
+ const entries = parsed.entries;
232
+ if (!Array.isArray(entries)) {
233
+ return err$1(
234
+ manifestMalformed({
235
+ message: "ShaderRegistry: manifest.entries is not an array",
236
+ hint: "manifest schema requires `entries: ManifestEntry[]`"
237
+ })
238
+ );
239
+ }
240
+ const validatedEntries = [];
241
+ for (const entry of entries) {
242
+ if (!isValidManifestEntry(entry)) {
243
+ return err$1(
244
+ manifestMalformed({
245
+ message: "ShaderRegistry: manifest entry missing required fields",
246
+ hint: "every entry needs {hash, wgsl, glsl, bindings} per @forgeax/engine-types.ManifestEntry",
247
+ reason: `bad entry: ${JSON.stringify(entry)}`
248
+ })
249
+ );
250
+ }
251
+ validatedEntries.push(entry);
252
+ }
253
+ const parsedSlim = parsed;
254
+ const validatedMaterialShaderEntries = [];
255
+ if (Array.isArray(parsedSlim.materialShaders)) {
256
+ for (const ms of parsedSlim.materialShaders) {
257
+ if (!isValidMaterialShaderManifestEntry(ms)) {
258
+ return err$1(
259
+ manifestMalformed({
260
+ message: "ShaderRegistry: manifest material shader entry missing required fields",
261
+ hint: "every material shader entry needs {identifier, sourcePath, composedWgsl, paramSchema, variants}",
262
+ reason: `bad material shader entry: ${JSON.stringify(ms)}`
263
+ })
264
+ );
265
+ }
266
+ validatedMaterialShaderEntries.push(ms);
267
+ }
268
+ }
269
+ this.#entries.clear();
270
+ for (const entry of validatedEntries) {
271
+ this.#entries.set(entry.hash, entry);
272
+ }
273
+ this.#materialShaderManifestEntries.length = 0;
274
+ this.#materialShaderManifestEntries.push(...validatedMaterialShaderEntries);
275
+ this.#manifestLoaded = true;
276
+ return ok$1(void 0);
277
+ }
278
+ /**
279
+ * Iterate every `ManifestEntry` populated by the most recent successful
280
+ * `loadManifest()` call. Order matches the manifest JSON `entries` array
281
+ * (insertion order; charter P2 structured-over-prose: AI users walk this
282
+ * to discover engine-shipped entries by content marker without
283
+ * re-fetching the manifest).
284
+ *
285
+ * Returns an empty iterator when no manifest has been loaded yet
286
+ * (charter P3 explicit failure: the empty case is not an error — the
287
+ * absent SSOT is the absent contract).
288
+ */
289
+ entries() {
290
+ return this.#entries.values();
291
+ }
292
+ /**
293
+ * Looks up a hash and forwards to `device.createShaderModule` (creates lazily
294
+ * on first hit, then caches).
295
+ *
296
+ * Error paths:
297
+ * - hash miss → `Result.err(ShaderError.shader-not-found)`
298
+ * - device.createShaderModule failed → `Result.err(RhiError)` passes through
299
+ * (it is **not** wrapped as a ShaderError).
300
+ *
301
+ * Sync shape: on cache miss, the underlying `device.createShaderModule` is
302
+ * called synchronously; the caller is responsible for handling the async
303
+ * real path at the device-wrapper layer (e.g. the engine pre-compiles
304
+ * during `loadManifest`).
305
+ */
306
+ get(hash) {
307
+ const cachedModule = this.#moduleCache.get(hash);
308
+ if (cachedModule !== void 0) {
309
+ return ok$1(cachedModule);
310
+ }
311
+ const entry = this.#entries.get(hash);
312
+ if (entry === void 0) {
313
+ return err$1(
314
+ shaderNotFound({
315
+ hash,
316
+ hint: "verify manifest.json contains the expected hash; rerun build to regenerate manifest"
317
+ })
318
+ );
319
+ }
320
+ const result = this.#device.createShaderModule({ code: entry.wgsl, label: hash });
321
+ if (!result.ok) {
322
+ return result;
323
+ }
324
+ this.#moduleCache.set(hash, result.value);
325
+ return result;
326
+ }
327
+ // ─── MaterialShader registry surface ───────────────────────────────────────
328
+ // (feat-20260523-shader-template-instance-split M5 / T05 — plan-strategy
329
+ // D-DefaultStandardPbr-Identifier).
330
+ //
331
+ // Two-tier identifier namespace:
332
+ // - `forgeax::*` — engine-shipped reserved prefix; the host wires
333
+ // `forgeax::default-standard-pbr` at engine boot from the
334
+ // vite-plugin-shader manifest's `materialShaders[]` row + the
335
+ // `default-standard-pbr.schema.json` SSOT (M5 / T08 + M6).
336
+ // - any other identifier — user-side custom material shader, registered
337
+ // after asset import.
338
+ //
339
+ // The registry is the runtime SSOT consumed by:
340
+ // - the material decoder's `'::' two-way dispatch` (M4 / T01) — the path
341
+ // branch validates against `paramSchema` and feeds `MaterialSnapshot.
342
+ // materialShaderId` (M4 / T05).
343
+ // - `render-system-record`'s pipeline cache key (M4 / T06) — same
344
+ // identifier feeds the per-pipeline `(materialShaderId, stateHash)` key.
345
+ /**
346
+ * Register a material shader entry under a stable identifier.
347
+ *
348
+ * **Throws** on:
349
+ * - duplicate registration of the same identifier (programmer error per
350
+ * AGENTS.md "explicit registration" + Inspector "fail-fast no overwrite"
351
+ * pattern; charter P3 explicit failure on misuse).
352
+ *
353
+ * Successful path returns void — the registry mutates in-place. Use
354
+ * {@link findMaterialArtifact} to read back.
355
+ *
356
+ * Identifier conventions (charter F1 / plan-strategy §8 §2):
357
+ * - `forgeax::<kebab-case>` — engine-shipped reserved prefix; the host
358
+ * registers `forgeax::default-standard-pbr` at boot.
359
+ * - `<package>::<id>` or `<guid>` — user-side custom shaders.
360
+ *
361
+ * @example
362
+ * ```ts
363
+ * const registry = new ShaderRegistry({ device, manifestUrl });
364
+ * registry.installMaterialArtifact('forgeax::default-standard-pbr', {
365
+ * source: composedWgsl,
366
+ * paramSchema: defaultStandardPbrSchema,
367
+ * });
368
+ * ```
369
+ */
370
+ installMaterialArtifact(identifier, entry) {
371
+ if (this.#materialShaders.has(identifier)) {
372
+ throw new Error(
373
+ `ShaderRegistry: material shader identifier '${identifier}' already registered; same-name re-register is forbidden (AGENTS.md "explicit registration" + Inspector "fail-fast no overwrite" pattern). Use findMaterialArtifact to read back the existing entry.`
374
+ );
375
+ }
376
+ const paramSchemaProjection = this.#paramSchemaProjectionOwner.admit({
377
+ ownerId: identifier,
378
+ revision: 1,
379
+ schema: entry.paramSchema
380
+ });
381
+ if (!identifier.startsWith(FORGEAX_RESERVED_PATH_PREFIX)) {
382
+ const undeclared = findUndeclaredSampledTextures(entry.source, paramSchemaProjection.schema);
383
+ if (undeclared.length > 0) {
384
+ throw new Error(
385
+ `ShaderRegistry: material shader '${identifier}' samples texture(s) [${undeclared.join(", ")}] in its WGSL but its paramSchema does not declare them as texture entries. Add { name: '${undeclared[0]}', type: 'texture2d' } (and any others listed) to the paramSchema, or the engine would silently bind the default white texture (charter P3 explicit failure; see docs/handover/2026-06-19-blending-transparency-regression-bisect.md).`
386
+ );
387
+ }
388
+ }
389
+ this.#materialShaders.set(
390
+ identifier,
391
+ Object.freeze({
392
+ source: entry.source,
393
+ paramSchema: paramSchemaProjection.schema,
394
+ paramSchemaProjection
395
+ })
396
+ );
397
+ }
398
+ /**
399
+ * Lookup a previously-registered material shader entry by identifier.
400
+ *
401
+ * Returns `Result.ok(entry)` on hit, `Result.err(material-shader-not-found)`
402
+ * on miss. The `Result` shape mirrors `get(hash)` for consistent
403
+ * abstraction across the runtime registry surface (charter P4).
404
+ *
405
+ * @example
406
+ * ```ts
407
+ * const r = registry.findMaterialArtifact('forgeax::default-standard-pbr');
408
+ * if (!r.ok) return handleError(r.error);
409
+ * const { source, paramSchema } = r.value;
410
+ * ```
411
+ */
412
+ findMaterialArtifact(identifier) {
413
+ const entry = this.#materialShaders.get(identifier);
414
+ if (entry === void 0) {
415
+ return err$1(
416
+ materialShaderNotFound({
417
+ identifier,
418
+ expected: Array.from(this.#materialShaders.keys()),
419
+ hint: `register the shader via ShaderRegistry.installMaterialArtifact('${identifier}', ...) at engine boot, or grep '${FORGEAX_RESERVED_PATH_PREFIX}' to enumerate engine-shipped reserved identifiers`
420
+ })
421
+ );
422
+ }
423
+ return ok$1(entry);
424
+ }
425
+ /**
426
+ * Iterate every registered material shader identifier in registration
427
+ * order (charter F1 grep gate: `for (const id of registry.materialShaderIdentifiers())`
428
+ * lists every `forgeax::*` + user shader without a private-field reach-in).
429
+ */
430
+ materialShaderIdentifiers() {
431
+ return this.#materialShaders.keys();
432
+ }
433
+ /** Runtime diagnostic counters for the immutable ParamSchema owner. */
434
+ paramSchemaProjectionStats() {
435
+ return this.#paramSchemaProjectionOwner.stats();
436
+ }
437
+ /**
438
+ * Iterate every MaterialShaderManifestEntry parsed from manifest.json's
439
+ * `materialShaders[]` field during `loadManifest()`.
440
+ *
441
+ * feat-20260526-pbr-uniform-fallback-no-storage-buffer M3 / w12:
442
+ * createRenderer variant resolution consumes this list to select the
443
+ * correct variant (by `caps.storageBuffer`) and register the resolved
444
+ * WGSL via `installMaterialArtifact`.
445
+ *
446
+ * Returns an empty iterator when no manifest has been loaded yet or
447
+ * the manifest lacks `materialShaders` (backward-compatible).
448
+ */
449
+ materialShaderManifestEntries() {
450
+ return this.#materialShaderManifestEntries.values();
451
+ }
452
+ };
453
+ function isValidManifestEntry(value) {
454
+ if (typeof value !== "object" || value === null) return false;
455
+ const v = value;
456
+ if (typeof v.hash !== "string") return false;
457
+ if (typeof v.wgsl !== "string") return false;
458
+ if (v.glsl !== void 0 && v.glsl !== null && typeof v.glsl !== "string") return false;
459
+ if (typeof v.bindings !== "string") return false;
460
+ return true;
461
+ }
462
+ function isValidMaterialShaderManifestEntry(value) {
463
+ if (typeof value !== "object" || value === null) return false;
464
+ const v = value;
465
+ if (typeof v.identifier !== "string") return false;
466
+ if (typeof v.sourcePath !== "string") return false;
467
+ if (typeof v.composedWgsl !== "string") return false;
468
+ if (typeof v.paramSchema !== "string") return false;
469
+ if (!Array.isArray(v.variants)) return false;
470
+ if (!v.variants.every(isValidMaterialShaderManifestVariant)) return false;
471
+ return v.uvSetCount === void 0 || typeof v.uvSetCount === "number";
472
+ }
473
+ function isValidMaterialShaderManifestVariant(value) {
474
+ if (typeof value !== "object" || value === null) return false;
475
+ const v = value;
476
+ if (typeof v.definesKey !== "string") return false;
477
+ if (typeof v.composedWgsl !== "string") return false;
478
+ if (typeof v.defines !== "object" || v.defines === null || Array.isArray(v.defines)) {
479
+ return false;
480
+ }
481
+ return Object.values(v.defines).every((define) => typeof define === "boolean");
482
+ }
483
+
484
+ // src/types.ts
485
+ function findVariantByKey(entry, definesKey) {
486
+ return entry.variants.find((v) => v.definesKey === definesKey);
487
+ }
488
+
489
+ // src/tonemap.ts
490
+ var TONEMAP_SHADER_MODE = {
491
+ none: 0,
492
+ reinhardExtended: 1,
493
+ linear: 2,
494
+ cineon: 3,
495
+ acesFilmic: 4,
496
+ agx: 5,
497
+ neutral: 6,
498
+ reinhard: 7
499
+ };
500
+
501
+ // src/index.ts
502
+ var BUILTIN_MATERIAL_MODULES = {
503
+ standard: "forgeax_material::standard",
504
+ unlit: "forgeax_material::unlit",
505
+ sprite: "forgeax_material::sprite"
506
+ };
507
+ var ENGINE_MATERIAL_MODULES = [
508
+ "forgeax::default-standard-pbr",
509
+ "forgeax::pbr-skin",
510
+ "forgeax::default-standard-pbr-skin",
511
+ "forgeax::default-unlit",
512
+ "forgeax::default-shadow-caster",
513
+ "forgeax::sprite",
514
+ "forgeax::sprite-lit",
515
+ "forgeax::msdf-text",
516
+ BUILTIN_MATERIAL_MODULES.standard,
517
+ BUILTIN_MATERIAL_MODULES.unlit,
518
+ BUILTIN_MATERIAL_MODULES.sprite,
519
+ "forgeax_material::sprite-lit"
520
+ ];
521
+ function isEngineMaterialModule(module) {
522
+ return ENGINE_MATERIAL_MODULES.includes(module);
523
+ }
524
+ function isEngineMaterial(material) {
525
+ const passes = material.passes ?? [];
526
+ return passes.length > 0 && passes.every((pass) => isEngineMaterialModule(pass.program.module));
527
+ }
528
+ var BUILTIN_PARAMETERS = {
529
+ standard: [
530
+ { name: "baseColor", type: "color" },
531
+ { name: "metallic", type: "f32" },
532
+ { name: "roughness", type: "f32" },
533
+ { name: "alphaCutoff", type: "f32", optional: true }
534
+ ],
535
+ unlit: [{ name: "baseColor", type: "color" }],
536
+ sprite: [{ name: "colorTint", type: "vec4", colorSpace: "srgb" }]
537
+ };
538
+ var BUILTIN_VALUES = {
539
+ standard: {
540
+ baseColor: [1, 1, 1, 1],
541
+ metallic: 0,
542
+ roughness: 0.5,
543
+ alphaCutoff: STANDARD_PBR_ALPHA_CUTOFF_DEFAULT
544
+ },
545
+ unlit: { baseColor: [1, 1, 1, 1] },
546
+ sprite: { colorTint: [1, 1, 1, 1] }
547
+ };
548
+ function createBuiltinMaterialAsset(kind) {
549
+ return {
550
+ kind: "material",
551
+ passes: [{ name: "forward", program: { module: BUILTIN_MATERIAL_MODULES[kind] } }],
552
+ parameters: BUILTIN_PARAMETERS[kind],
553
+ values: BUILTIN_VALUES[kind]
554
+ };
555
+ }
556
+ var TONEMAP_LUMINANCE_EPSILON = 1e-5;
557
+
558
+ export { BUILTIN_MATERIAL_MODULES, DEFAULT_MSDF_TEXT_PARAM_SCHEMA, DEFAULT_SPRITE_PARAM_SCHEMA, DEFAULT_STANDARD_PBR_PARAM_SCHEMA, DEFAULT_UNLIT_PARAM_SCHEMA, ENGINE_MATERIAL_MODULES, FORGEAX_RESERVED_PATH_PREFIX, MaterialArtifactRegistry, STANDARD_PBR_ALPHA_CUTOFF_DEFAULT, ShaderError, ShaderRegistry, TONEMAP_LUMINANCE_EPSILON, TONEMAP_SHADER_MODE, createBuiltinMaterialAsset, findVariantByKey, isEngineMaterial, isEngineMaterialModule, isMaterialShaderArtifact, manifestMalformed, materialShaderNotFound, registerDefaultSpriteLit, registerDefaultStandardPbrSkin, registerMaterialArtifact, shaderNotFound };
559
+ //# sourceMappingURL=index.mjs.map
560
+ //# sourceMappingURL=index.mjs.map