@forgeax/engine-shader 0.0.0-dev.8d955ade1c79

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