@carbonenginejs/runtime-resource 0.1.0 → 0.7.0

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 (186) hide show
  1. package/FORMAT-PROVENANCE.md +14 -5
  2. package/README.md +537 -241
  3. package/dist/CjsMotherLode.js +1244 -29
  4. package/dist/CjsMotherLode.js.map +1 -1
  5. package/dist/CjsResMan.js +2537 -272
  6. package/dist/CjsResMan.js.map +1 -1
  7. package/dist/CjsResource.js +218 -18
  8. package/dist/CjsResource.js.map +1 -1
  9. package/dist/format/CjsBlueReader.js +269 -0
  10. package/dist/format/CjsBlueReader.js.map +1 -0
  11. package/dist/format/CjsFormat.js +44 -30
  12. package/dist/format/CjsFormat.js.map +1 -1
  13. package/dist/format/CjsReader.js +18 -0
  14. package/dist/format/CjsReader.js.map +1 -0
  15. package/dist/format/CjsResourceProbe.js +12 -4
  16. package/dist/format/CjsResourceProbe.js.map +1 -1
  17. package/dist/formats/black/CjsBlackFormat.js +12 -6
  18. package/dist/formats/black/CjsBlackFormat.js.map +1 -1
  19. package/dist/formats/black/core/CjsBlackReader.js +27 -148
  20. package/dist/formats/black/core/CjsBlackReader.js.map +1 -1
  21. package/dist/formats/bnk/CjsBnkFormat.js +140 -147
  22. package/dist/formats/bnk/CjsBnkFormat.js.map +1 -1
  23. package/dist/formats/bnk/core/graph.js +140 -0
  24. package/dist/formats/bnk/core/graph.js.map +1 -0
  25. package/dist/formats/bnk/core/helpers.js +58 -3
  26. package/dist/formats/bnk/core/helpers.js.map +1 -1
  27. package/dist/formats/bnk/core/musicNodes.js +521 -0
  28. package/dist/formats/bnk/core/musicNodes.js.map +1 -0
  29. package/dist/formats/bnk/index.js +0 -1
  30. package/dist/formats/bnk/index.js.map +1 -1
  31. package/dist/formats/cmf/CjsCmfFormat.js +14 -8
  32. package/dist/formats/cmf/CjsCmfFormat.js.map +1 -1
  33. package/dist/formats/dds/CjsDdsFormat.js +12 -6
  34. package/dist/formats/dds/CjsDdsFormat.js.map +1 -1
  35. package/dist/formats/fbx/CjsFbxFormat.js +12 -6
  36. package/dist/formats/fbx/CjsFbxFormat.js.map +1 -1
  37. package/dist/formats/flac/CjsFlacFormat.js +10 -4
  38. package/dist/formats/flac/CjsFlacFormat.js.map +1 -1
  39. package/dist/formats/gif/CjsGifFormat.js +11 -5
  40. package/dist/formats/gif/CjsGifFormat.js.map +1 -1
  41. package/dist/formats/gltf/CjsGltfFormat.js +12 -6
  42. package/dist/formats/gltf/CjsGltfFormat.js.map +1 -1
  43. package/dist/formats/jpeg/CjsJpegFormat.js +11 -5
  44. package/dist/formats/jpeg/CjsJpegFormat.js.map +1 -1
  45. package/dist/formats/mp3/CjsMp3Format.js +11 -5
  46. package/dist/formats/mp3/CjsMp3Format.js.map +1 -1
  47. package/dist/formats/mp4/CjsMp4Format.js +10 -4
  48. package/dist/formats/mp4/CjsMp4Format.js.map +1 -1
  49. package/dist/formats/obj/CjsObjFormat.js +12 -6
  50. package/dist/formats/obj/CjsObjFormat.js.map +1 -1
  51. package/dist/formats/ogg/CjsOggFormat.js +9 -3
  52. package/dist/formats/ogg/CjsOggFormat.js.map +1 -1
  53. package/dist/formats/png/CjsPngFormat.js +11 -5
  54. package/dist/formats/png/CjsPngFormat.js.map +1 -1
  55. package/dist/formats/red/CjsRedFormat.js +19 -5
  56. package/dist/formats/red/CjsRedFormat.js.map +1 -1
  57. package/dist/formats/red/core/CjsRedReader.js +63 -70
  58. package/dist/formats/red/core/CjsRedReader.js.map +1 -1
  59. package/dist/formats/red/core/helpers.js +3 -0
  60. package/dist/formats/red/core/helpers.js.map +1 -1
  61. package/dist/formats/stl/CjsStlFormat.js +77 -16
  62. package/dist/formats/stl/CjsStlFormat.js.map +1 -1
  63. package/dist/formats/stl/core/helpers.js +4 -0
  64. package/dist/formats/stl/core/helpers.js.map +1 -1
  65. package/dist/formats/stl/core/stl.js +100 -15
  66. package/dist/formats/stl/core/stl.js.map +1 -1
  67. package/dist/formats/tga/CjsTgaFormat.js +11 -5
  68. package/dist/formats/tga/CjsTgaFormat.js.map +1 -1
  69. package/dist/formats/wav/CjsWavFormat.js +11 -5
  70. package/dist/formats/wav/CjsWavFormat.js.map +1 -1
  71. package/dist/formats/webm/CjsWebmFormat.js +10 -4
  72. package/dist/formats/webm/CjsWebmFormat.js.map +1 -1
  73. package/dist/formats/webp/CjsWebpFormat.js +9 -3
  74. package/dist/formats/webp/CjsWebpFormat.js.map +1 -1
  75. package/dist/formats/wem/CjsWemFormat.js +50 -6
  76. package/dist/formats/wem/CjsWemFormat.js.map +1 -1
  77. package/dist/formats/wem/core/helpers.js +84 -3
  78. package/dist/formats/wem/core/helpers.js.map +1 -1
  79. package/dist/formats/wem/core/ptadpcm.js +77 -0
  80. package/dist/formats/wem/core/ptadpcm.js.map +1 -0
  81. package/dist/formats/wem/core/resolve.js +121 -0
  82. package/dist/formats/wem/core/resolve.js.map +1 -0
  83. package/dist/formats/yaml/CjsYamlFormat.js +11 -5
  84. package/dist/formats/yaml/CjsYamlFormat.js.map +1 -1
  85. package/dist/formats/yaml/core/CjsYamlReader.js +3 -2
  86. package/dist/formats/yaml/core/CjsYamlReader.js.map +1 -1
  87. package/dist/index.js +28 -32
  88. package/dist/index.js.map +1 -1
  89. package/dist/{generated/resources → resources}/AudioGeometryResData.js +2 -2
  90. package/dist/resources/AudioGeometryResData.js.map +1 -0
  91. package/dist/{generated/resources → resources}/GStateBindingCallbackData.js +2 -2
  92. package/dist/resources/GStateBindingCallbackData.js.map +1 -0
  93. package/dist/{generated/resources → resources}/MeshDecalData.js +2 -2
  94. package/dist/resources/MeshDecalData.js.map +1 -0
  95. package/dist/{generated/resources → resources}/MeshDecalLodData.js +2 -2
  96. package/dist/resources/MeshDecalLodData.js.map +1 -0
  97. package/dist/resources/Tr2GrannyIntersectionResult.js +60 -0
  98. package/dist/resources/Tr2GrannyIntersectionResult.js.map +1 -0
  99. package/dist/{generated/resources → resources}/Tr2MaterialArea.js +2 -2
  100. package/dist/resources/Tr2MaterialArea.js.map +1 -0
  101. package/dist/{generated/resources → resources}/Tr2MaterialMesh.js +2 -2
  102. package/dist/resources/Tr2MaterialMesh.js.map +1 -0
  103. package/dist/{generated/resources → resources}/Tr2MaterialRes.js +2 -2
  104. package/dist/resources/Tr2MaterialRes.js.map +1 -0
  105. package/dist/{generated/resources → resources}/Tr2ShaderPermutation.js +2 -2
  106. package/dist/resources/Tr2ShaderPermutation.js.map +1 -0
  107. package/dist/{generated/resources → resources}/Tr2TextureLodManager.js +50 -10
  108. package/dist/resources/Tr2TextureLodManager.js.map +1 -0
  109. package/dist/{generated/resources → resources}/Tr2TextureLodUpdateRequest.js +2 -2
  110. package/dist/resources/Tr2TextureLodUpdateRequest.js.map +1 -0
  111. package/dist/{generated/resources → resources}/Tr2TexturePackChannel.js +2 -2
  112. package/dist/resources/Tr2TexturePackChannel.js.map +1 -0
  113. package/dist/resources/Tr2TexturePipeline.js +68 -0
  114. package/dist/resources/Tr2TexturePipeline.js.map +1 -0
  115. package/dist/{generated/resources → resources}/Tr2TexturePipelineParams.js +2 -2
  116. package/dist/resources/Tr2TexturePipelineParams.js.map +1 -0
  117. package/dist/{generated/resources → resources}/Tr2TexturePipelineStepCompress.js +3 -3
  118. package/dist/resources/Tr2TexturePipelineStepCompress.js.map +1 -0
  119. package/dist/{generated/resources → resources}/Tr2TexturePipelineStepLimitSize.js +2 -2
  120. package/dist/resources/Tr2TexturePipelineStepLimitSize.js.map +1 -0
  121. package/dist/{generated/resources → resources}/Tr2TexturePipelineStepLoad.js +2 -2
  122. package/dist/resources/Tr2TexturePipelineStepLoad.js.map +1 -0
  123. package/dist/{generated/resources → resources}/Tr2TexturePipelineStepPack.js +3 -3
  124. package/dist/resources/Tr2TexturePipelineStepPack.js.map +1 -0
  125. package/dist/{generated/resources → resources}/TriGeometryResAreaData.js +2 -2
  126. package/dist/resources/TriGeometryResAreaData.js.map +1 -0
  127. package/dist/{generated/resources → resources}/TriGeometryResJointData.js +2 -2
  128. package/dist/resources/TriGeometryResJointData.js.map +1 -0
  129. package/dist/{generated/resources → resources}/TriGeometryResLodData.js +2 -2
  130. package/dist/resources/TriGeometryResLodData.js.map +1 -0
  131. package/dist/{generated/resources → resources}/TriGeometryResMeshData.js +2 -2
  132. package/dist/resources/TriGeometryResMeshData.js.map +1 -0
  133. package/dist/{generated/resources → resources}/TriGeometryResSkeletonData.js +2 -2
  134. package/dist/resources/TriGeometryResSkeletonData.js.map +1 -0
  135. package/dist/{generated/resources → resources}/TriJointBinding.js +2 -2
  136. package/dist/resources/TriJointBinding.js.map +1 -0
  137. package/dist/{generated/resources → resources}/TriMorphTargetGeometryConstants.js +2 -2
  138. package/dist/resources/TriMorphTargetGeometryConstants.js.map +1 -0
  139. package/dist/{generated/resources → resources}/TriRtGeometryConstants.js +2 -2
  140. package/dist/resources/TriRtGeometryConstants.js.map +1 -0
  141. package/dist/resources/enums.js.map +1 -0
  142. package/dist/resources/texturePipelineBehavior.js +320 -0
  143. package/dist/resources/texturePipelineBehavior.js.map +1 -0
  144. package/format-notices/bnk/NOTICE +7 -4
  145. package/package.json +51 -51
  146. package/resource-lifecycle.md +262 -70
  147. package/dist/generated/resources/AudioGeometryResData.js.map +0 -1
  148. package/dist/generated/resources/CmfVertexReader.js +0 -46
  149. package/dist/generated/resources/CmfVertexReader.js.map +0 -1
  150. package/dist/generated/resources/GStateBindingCallbackData.js.map +0 -1
  151. package/dist/generated/resources/MeshDecalData.js.map +0 -1
  152. package/dist/generated/resources/MeshDecalLodData.js.map +0 -1
  153. package/dist/generated/resources/Tr2AsyncSave.js +0 -46
  154. package/dist/generated/resources/Tr2AsyncSave.js.map +0 -1
  155. package/dist/generated/resources/Tr2CmfContents.js +0 -37
  156. package/dist/generated/resources/Tr2CmfContents.js.map +0 -1
  157. package/dist/generated/resources/Tr2GrannyIntersectionResult.js +0 -58
  158. package/dist/generated/resources/Tr2GrannyIntersectionResult.js.map +0 -1
  159. package/dist/generated/resources/Tr2LoadPrepareFence.js +0 -37
  160. package/dist/generated/resources/Tr2LoadPrepareFence.js.map +0 -1
  161. package/dist/generated/resources/Tr2MaterialArea.js.map +0 -1
  162. package/dist/generated/resources/Tr2MaterialMesh.js.map +0 -1
  163. package/dist/generated/resources/Tr2MaterialRes.js.map +0 -1
  164. package/dist/generated/resources/Tr2ShaderPermutation.js.map +0 -1
  165. package/dist/generated/resources/Tr2TextureLodManager.js.map +0 -1
  166. package/dist/generated/resources/Tr2TextureLodUpdateRequest.js.map +0 -1
  167. package/dist/generated/resources/Tr2TexturePackChannel.js.map +0 -1
  168. package/dist/generated/resources/Tr2TexturePipeline.js +0 -44
  169. package/dist/generated/resources/Tr2TexturePipeline.js.map +0 -1
  170. package/dist/generated/resources/Tr2TexturePipelineParams.js.map +0 -1
  171. package/dist/generated/resources/Tr2TexturePipelineStepCompress.js.map +0 -1
  172. package/dist/generated/resources/Tr2TexturePipelineStepLimitSize.js.map +0 -1
  173. package/dist/generated/resources/Tr2TexturePipelineStepLoad.js.map +0 -1
  174. package/dist/generated/resources/Tr2TexturePipelineStepPack.js.map +0 -1
  175. package/dist/generated/resources/TriGeometryResAreaData.js.map +0 -1
  176. package/dist/generated/resources/TriGeometryResJointData.js.map +0 -1
  177. package/dist/generated/resources/TriGeometryResLodData.js.map +0 -1
  178. package/dist/generated/resources/TriGeometryResMeshData.js.map +0 -1
  179. package/dist/generated/resources/TriGeometryResSkeletonData.js.map +0 -1
  180. package/dist/generated/resources/TriJointBinding.js.map +0 -1
  181. package/dist/generated/resources/TriMorphTargetGeometryConstants.js.map +0 -1
  182. package/dist/generated/resources/TriRtGeometryConstants.js.map +0 -1
  183. package/dist/generated/resources/enums.js.map +0 -1
  184. package/dist/generated/resources/index.js +0 -32
  185. package/dist/generated/resources/index.js.map +0 -1
  186. /package/dist/{generated/resources → resources}/enums.js +0 -0
package/README.md CHANGED
@@ -1,241 +1,537 @@
1
- # @carbonenginejs/runtime-resource
2
-
3
- CarbonEngineJS resource lifecycle, cache, format selection, source, and object
4
- loading contracts.
5
-
6
- This package owns the GPU-free resource layer:
7
-
8
- - `CjsResource` state and Carbon-style resource methods.
9
- - `CjsTextureArrayRes` and `CjsTextureParameterProxy` for material-facing,
10
- frame-coalesced texture-array inputs without changing ordinary texture
11
- parameter behavior.
12
- - `CjsMotherLode` cache lookup/insert/delete/stats.
13
- - `CjsResMan` semantic resource construction, registered-format selection,
14
- concurrency-limited source loading, staged prepare queues, layered
15
- source/read/resource deduplication, object loader dispatch, and prefetch.
16
- - Raw `CjsEventEmitter` from `core-types/model` for manager/runtime events
17
- without requiring `CjsModel` inheritance. External listeners unregister
18
- directly with `OffEvent`; listener scopes, owner-side `ListenTo` helpers,
19
- and a separate resource notification layer are not part of the contract.
20
- - Path normalization and extension helpers.
21
- - Source adapters for memory and `fetch`.
22
- - Plain reader/converter payload objects with focused shared validators.
23
- - Canonical Carbon resource classes that validate and hold CPU payloads
24
- privately:
25
- `TriTextureRes`, `TriGeometryRes`, `Tr2EffectRes`, `Tr2ImageRes`,
26
- `TriGrannyRes`, `Tr2GrannyStateRes`, and `Tr2LightProfileRes`.
27
- - Opaque engine-owned subobject slots for backend adapters.
28
- - Format policy, format class contracts, and load/prepare state mapping stay
29
- inside this package's implementation and public API rather than external
30
- workspace notes.
31
- - Non-shader format implementations are owned as explicit tree-shakeable
32
- subpaths under `@carbonenginejs/runtime-resource/formats/<name>`.
33
-
34
- It intentionally does not own WebGL/WebGPU realization. Engine packages should adapt prepared resources into backend objects.
35
-
36
- Authoring source is decorated JavaScript. Published/consumer output is built ESM in `npm/dist`.
37
-
38
- ## Package relationships
39
-
40
- - `runtime-core` may configure and expose a `CjsResMan`, but does not own its
41
- implementation.
42
- - `runtime-trinity` and `runtime-sof` may request GPU-free objects and resources
43
- without selecting an engine.
44
- - `engine-webgpu` and future WebGL engines consume loaded resources and own all
45
- backend allocations, preparation, replacement, and destruction.
46
-
47
- Concrete formats are not imported or registered by the package root:
48
-
49
- ```js
50
- import { CjsResMan } from "@carbonenginejs/runtime-resource";
51
- import { CjsMp4Format } from "@carbonenginejs/runtime-resource/formats/mp4";
52
-
53
- const resMan = new CjsResMan().Register({
54
- source,
55
- formats: [ CjsMp4Format ]
56
- });
57
-
58
- const resource = resMan.GetResource("res:/video/intro.mp4");
59
- const video = await resource.Ready();
60
- ```
61
-
62
- Formats return plain payload objects. Semantic resource classes apply them
63
- through `SetPayload()`, validate their own required fields, and throw
64
- `CJS_RESOURCE_PAYLOAD_INVALID` before replacing a previously valid payload.
65
- `GetPayload()`, `HasPayload()`, and `ReleasePayload()` manage transient CPU
66
- retention without introducing a parallel DTO class hierarchy.
67
-
68
- ## Queued load and staged prepare
69
-
70
- `GetObject()`, `LoadObject()`, and resource `Ready()` use two manager-owned
71
- queues:
72
-
73
- ```text
74
- BACKGROUND: deduplicated source load, limited by maxConcurrentLoads
75
- MAIN: read -> configured prepare stages -> resource publication
76
- ```
77
-
78
- Each main-queue stage is a separate item. `maxPrepareTime` is a per-pump budget
79
- in seconds, and `maxPrepareItemsPerTick` can add an item-count limit. The
80
- default scheduler keeps promise-based calls working; a `CjsLibrary` or direct
81
- caller can provide its frame scheduler and default build behavior:
82
-
83
- ```js
84
- const resMan = new CjsResMan({
85
- source,
86
- maxConcurrentLoads: 8,
87
- maxPrepareTime: 0.005,
88
- queueScheduler: callback => requestAnimationFrame(callback),
89
- preparePipelines: {
90
- cmf_test: {
91
- default: true,
92
- stages: [
93
- {
94
- name: "convert",
95
- prepare: (payload, context) => convertToCmf(payload, context)
96
- }
97
- ]
98
- }
99
- }
100
- });
101
-
102
- await resMan.FetchResource("res:/model/ship.gr2", {
103
- requirement: "geometry",
104
- preparePipeline: "cmf_test"
105
- });
106
- ```
107
-
108
- The library chooses the named pipeline from registered behavior and detected
109
- capabilities. `CjsResMan` executes that request; it does not inspect WebGL,
110
- WebGPU, texture, geometry, or codec support to select one. A request may
111
- override the default with `preparePipeline` or append direct `prepareStages`.
112
-
113
- Blue-compatible queue controls are exposed directly on `CjsResMan`:
114
- `AddToQueue`, `CancelFromQueue`, `GetNextIdForQueue`,
115
- `PumpMainThreadQueue`, `PauseQueue`, `ResumeQueue`, `GetPendingLoads`, and
116
- `GetPendingPrepares`. `Update()`/`Tick()` pump work, while `Wait()` is the
117
- method-level queue fence; no separate fence object is required.
118
-
119
- Format classes own input extensions. Resource classes are registered by a
120
- semantic requirement, never by file extension:
121
-
122
- ```js
123
- const resMan = new CjsResMan().Register({
124
- source,
125
- formats: [ CjsDdsFormat, CjsPngFormat ],
126
- resourceTypes: [ TriTextureRes, Tr2ImageRes ]
127
- });
128
-
129
- const texture = resMan.GetResource("res:/image/ship.png", {
130
- requirement: "texture",
131
- emit: "image"
132
- });
133
- const image = resMan.GetResource("res:/image/ship.png", {
134
- requirement: "image",
135
- emit: "image"
136
- });
137
- ```
138
-
139
- Those are distinct resource identities but share the normalized source-byte
140
- operation. The manager does not expose an extension-to-resource compatibility
141
- registry.
142
-
143
- Texture-array resources expose one ordinary-looking proxy per ordered layer:
144
-
145
- ```js
146
- const textureArray = new CjsTextureArrayRes({
147
- paths: [
148
- "res:/detail1.dds",
149
- "res:/detail2.dds",
150
- "res:/detail3.dds"
151
- ],
152
- layerNames: [ "Detail1Map", "Detail2Map", "Detail3Map" ],
153
- updateScheduler: resource => frameQueue.add(resource)
154
- });
155
-
156
- const detail2 = textureArray.GetLayerParameter(1);
157
- detail2.SetValue("res:/replacement.dds");
158
-
159
- detail2.textureRes === textureArray; // true
160
- ```
161
-
162
- Proxy setters only update their source path and invalidate the parent. The
163
- parent is scheduled once even if several proxies change in the same frame.
164
- The next-frame consumer calls `Update()` or `ConsumeUpdateRequest()` to obtain
165
- one immutable ordered snapshot. Runtime-resource does not know which shader
166
- metadata caused the aggregate request; shader packages and engine adapters map
167
- public parameter names to layer indices.
168
-
169
- Public effect parameters remain separate from these internal proxies. Their
170
- authored paths and individual 2D source resources are not replaced by the
171
- aggregate. An engine-owned, non-persisted bridge mirrors public changes into
172
- the fixed internal layers.
173
-
174
- Consumed snapshots are explicit in-flight generations. An adapter either
175
- publishes the current candidate atomically, requeues retryable work, or records
176
- failure:
177
-
178
- ```js
179
- const request = textureArray.ConsumeUpdateRequest();
180
-
181
- try {
182
- const candidate = await adapter.PrepareTextureArray(request);
183
- const result = textureArray.CommitPreparedAdapterRevision(
184
- request.revision,
185
- "webgpu",
186
- candidate
187
- );
188
-
189
- // A rejected/stale candidate is destroyed by the commit method by default.
190
- // The adapter owns disposal of a successfully displaced allocation.
191
- result.displaced?.destroy();
192
- } catch (error) {
193
- textureArray.FailUpdateRequest(request.revision, error, { retry: true });
194
- }
195
-
196
- await textureArray.Ready(); // the generation requested at call time
197
- ```
198
-
199
- `SetLayerResource()` attaches a resolved source without rewriting the logical
200
- requested path. `TouchLayer()` invalidates an in-place source revision.
201
- `RetryUpdateRequest()` restores consumed work, and `HandleAdapterLoss()` drops
202
- an unusable adapter allocation and schedules a complete topology rebuild.
203
- Topology-changing snapshots set `topologyChanged: true` and report only valid
204
- current layer indices in `dirtyLayers`.
205
-
206
- ## Development
207
-
208
- Install dependencies and run the non-interactive baseline checks from the
209
- repository root:
210
-
211
- ```sh
212
- npm install
213
- npm run lint
214
- npm run check
215
- npm test
216
- ```
217
-
218
- `npm run check` builds the consumer package and proves that decorator metadata
219
- matches between authoring source and built output. `npm test` additionally runs
220
- the complete GPU-free unit suite; it requires no private assets, credentials,
221
- network access, browser, or GPU after dependencies are installed.
222
-
223
- See [Runtime Resource Lifecycle](resource-lifecycle.md) for state, retention,
224
- and texture-array generation contracts. See
225
- [Format ownership and fork provenance](FORMAT-PROVENANCE.md) for copied-reader
226
- ownership, licenses, exclusions, and the deferred GR2 migration. Both documents
227
- ship with the published package.
228
-
229
- ## Provenance
230
-
231
- CarbonEngine and Fenris Creations (CCP Games) are named for interoperability
232
- and provenance context. This package contains CarbonEngineJS original resource
233
- infrastructure, CarbonEngine-shaped resource ports, and maintained copies of
234
- the non-shader readers identified in `FORMAT-PROVENANCE.md`. It does not copy
235
- Fenris Creations game assets, proprietary documentation, or shader source.
236
- CarbonEngine and historical JavaScript implementations were used as the
237
- behavioral references described in the package notices.
238
-
239
- This project is not affiliated with, endorsed by, or sponsored by CCP Games or
240
- CCP ehf. EVE Online and related marks remain the property of their respective
241
- owners.
1
+ # @carbonenginejs/runtime-resource
2
+
3
+ CarbonEngineJS resource lifecycle, cache, format selection, source, and object
4
+ loading contracts.
5
+
6
+ This package owns the GPU-free resource layer:
7
+
8
+ - `CjsResource` state and Carbon-style resource methods.
9
+ - `CjsTextureArrayRes` and `CjsTextureParameterProxy` for material-facing,
10
+ frame-coalesced texture-array inputs without changing ordinary texture
11
+ parameter behavior.
12
+ - `CjsMotherLode` canonical identity, explicit replacement results, activity
13
+ and lock metadata, deterministic payload/adapter cleanup, and cache stats.
14
+ - `CjsResMan` semantic resource construction, registered-format selection,
15
+ concurrency-limited source loading, staged prepare queues, layered
16
+ source/read/resource deduplication, object loader dispatch, and prefetch.
17
+ - Raw `CjsEventEmitter` from `core-types/model` for manager/runtime events
18
+ without requiring `CjsModel` inheritance. External listeners unregister
19
+ directly with `OffEvent`; listener scopes, owner-side `ListenTo` helpers,
20
+ and a separate resource notification layer are not part of the contract.
21
+ - Path normalization and extension helpers.
22
+ - Source adapters for memory and `fetch`.
23
+ - Plain reader/converter payload objects with focused shared validators.
24
+ - Canonical Carbon resource classes that validate and hold CPU payloads
25
+ privately:
26
+ `TriTextureRes`, `TriGeometryRes`, `Tr2EffectRes`, `Tr2ImageRes`,
27
+ `TriGrannyRes`, `Tr2GrannyStateRes`, and `Tr2LightProfileRes`.
28
+ - `Tr2TexturePipeline` for Carbon-shaped CPU-only load, limit-size, compress
29
+ validation, and channel-pack steps, plus `Tr2TextureLodManager` for explicit
30
+ texture-resource membership without device-memory policy.
31
+ - Opaque engine-owned subobject slots for backend adapters.
32
+ - Format policy, format class contracts, and load/prepare state mapping stay
33
+ inside this package's implementation and public API rather than external
34
+ workspace notes.
35
+ - Non-shader format implementations are owned as explicit tree-shakeable
36
+ subpaths under `@carbonenginejs/runtime-resource/formats/<name>`.
37
+
38
+ It intentionally does not own WebGL/WebGPU realization. Engine packages should adapt prepared resources into backend objects.
39
+
40
+ Authoring source is decorated JavaScript. Published/consumer output is built ESM in `npm/dist`.
41
+
42
+ Completed Carbon data classes live with maintained source under
43
+ `src/resources`; `src/generated` is reserved for unresolved active ports and is
44
+ currently absent. Native shapes that JavaScript replaces or does not use are
45
+ retained only under `src/dropped`, with their disposition documented there,
46
+ and are never exported or bundled.
47
+
48
+ ## Package relationships
49
+
50
+ - `runtime-core` may configure and expose a `CjsResMan`, but does not own its
51
+ implementation.
52
+ - `runtime-trinity` and `runtime-sof` may request GPU-free objects and resources
53
+ without selecting an engine.
54
+ - `engine-webgpu` and future WebGL engines consume loaded resources and own all
55
+ backend allocations, preparation, replacement, and destruction.
56
+
57
+ Concrete formats are not imported or registered by the package root:
58
+
59
+ ```js
60
+ import { CjsResMan } from "@carbonenginejs/runtime-resource";
61
+ import { CjsMp4Format } from "@carbonenginejs/runtime-resource/formats/mp4";
62
+
63
+ const resMan = new CjsResMan().Register({
64
+ source,
65
+ formats: [ CjsMp4Format ]
66
+ });
67
+
68
+ const resource = resMan.GetResource("res:/video/intro.mp4");
69
+ const video = await resource.Ready();
70
+ ```
71
+
72
+ Formats return plain payload objects. Semantic resource classes apply them
73
+ through `SetPayload()`, validate their own required fields, and throw
74
+ `CJS_RESOURCE_PAYLOAD_INVALID` before replacing a previously valid payload.
75
+ `GetPayload()`, `HasPayload()`, and `ReleasePayload()` manage transient CPU
76
+ retention without introducing a parallel DTO class hierarchy.
77
+
78
+ Red payload output reserves configurable type, ID, reference, and sequence
79
+ values markers (`_type`, `_id`, `_reference`, and `_values` by default).
80
+ Repeated or cyclic sequences use an ID-bearing values envelope; unique
81
+ sequences remain arrays. Authored fields may not collide with active markers,
82
+ so remap the marker options when those names are real data. Disabling the
83
+ reference marker preserves actual JavaScript identity; cyclic output in that
84
+ mode is intentionally not JSON-serializable.
85
+
86
+ ## Texture CPU pipeline and LOD membership
87
+
88
+ `Tr2TexturePipeline` is the Carbon texture-specific CPU bitmap pipeline, not a
89
+ general resource prepare stage. `GetResourceDependencies()` returns the sorted
90
+ unique paths required by load and channel-pack steps. `Execute()` resolves
91
+ those inputs from an explicit `inputs` map/object, an async `load(path)`
92
+ callback, or an injected `CjsResMan`, then returns a canonical plain
93
+ `rgba8unorm` payload:
94
+
95
+ ```js
96
+ import {
97
+ Tr2TexturePipeline,
98
+ Tr2TexturePipelineStepLoad,
99
+ Tr2TexturePipelineStepLimitSize
100
+ } from "@carbonenginejs/runtime-resource";
101
+
102
+ const load = new Tr2TexturePipelineStepLoad();
103
+ load.path = "res:/texture/source.png";
104
+ const limit = new Tr2TexturePipelineStepLimitSize();
105
+ limit.maxWidth = 512;
106
+
107
+ const pipeline = new Tr2TexturePipeline();
108
+ pipeline.steps = [ load, limit ];
109
+ const rgba = await pipeline.Execute(0, 0, { resMan });
110
+ ```
111
+
112
+ The maintained runtime path currently accepts canonical `rgba8unorm` inputs.
113
+ Load copies the source bitmap, limit-size repeatedly performs a 2x2 CPU
114
+ downsample, pack builds logical RGBA channels from independent inputs, and
115
+ Carbon's present compress step remains validation-only because the native
116
+ method is itself a no-op. Unsupported step types fail explicitly.
117
+
118
+ `Tr2TextureLodManager` owns only ordered resource membership through
119
+ `RegisterTexture()`, `UnregisterTexture()`, and `GetManagedTextures()`. Engine
120
+ packages continue to own GPU allocations, upload accounting, device budgets,
121
+ capability limits, and device-loss recovery.
122
+
123
+ ## Wwise soundbanks and media
124
+
125
+ `formats/bnk` and `formats/wem` cover the Wwise audio pipeline end to end.
126
+ `CjsBnkFormat.inspect()` decodes the chunk map, embedded media index, bank
127
+ names, and the HIRC listing with version-stable typed fields (event action
128
+ lists, action type/target, sound and music-track source ids; pinned against
129
+ bank generator version 150). The Wwise-domain toolkit is grouped under the
130
+ `CjsBnkFormat.wwise` static: the SoundbanksInfo catalog helpers, the FNV-1
131
+ id hash, and event → media resolution:
132
+
133
+ ```js
134
+ import { CjsBnkFormat } from "@carbonenginejs/runtime-resource/formats/bnk";
135
+ import { CjsWemFormat } from "@carbonenginejs/runtime-resource/formats/wem";
136
+
137
+ const inspections = bankByteArrays.map(bytes => CjsBnkFormat.inspect(bytes));
138
+ const { eventMedia } = CjsBnkFormat.wwise.eventMediaFromBanks(inspections);
139
+ // eventMedia: Map<eventObjectId, Set<wemId>> - banks may split events from
140
+ // their target sounds, so pass every related bank to one call.
141
+
142
+ const ogg = CjsWemFormat.toOgg(wemBytes); // Wwise Vorbis -> Ogg (lossless)
143
+ const pcm = CjsWemFormat.toPcm(wemBytes); // PTADPCM / 16-bit PCM -> float32
144
+ ```
145
+
146
+ The read/inspect path stays a pure container reader; `wwise.eventMediaFromBanks`
147
+ is graph interpretation offered for consumers with their own engines — the
148
+ resource lifecycle never calls it.
149
+
150
+ ## STL export
151
+
152
+ `CjsStlFormat` writes shared geometry directly to binary or ASCII STL. The
153
+ writer consumes `mesh.vertex.position` and triangular `mesh.indices[].faces`;
154
+ multiple meshes and index groups are flattened in encounter order because STL
155
+ does not carry portable scene, material, skin, or animation structure.
156
+
157
+ ```js
158
+ import { CjsStlFormat } from "@carbonenginejs/runtime-resource/formats/stl";
159
+
160
+ const bytes = CjsStlFormat.write(sharedGeometry, {
161
+ binary: true,
162
+ solidName: "ship_hull",
163
+ scale: 1000,
164
+ requireWatertight: true
165
+ });
166
+ ```
167
+
168
+ Writes do not mutate the shared input. Facet normals are recalculated from
169
+ winding by default; set `recalculateNormals: false` to average valid vertex
170
+ normals. Degenerate triangles are skipped by default. Index values must be safe
171
+ integers within the position channel, and binary output rejects coordinates
172
+ outside float32 range instead of silently emitting infinities. The
173
+ `requireWatertight` option rejects open, non-manifold, inconsistently wound, or
174
+ degenerate output.
175
+
176
+ ## MotherLode ownership
177
+
178
+ `CjsResMan` resolves each normalized path and promised output to one canonical
179
+ MotherLode key. `Insert(key, resource, options)` reports `{ inserted, replaced,
180
+ displaced }`; replacement, deletion, clearing, and shutdown destroy attached
181
+ adapter allocations and release the complete CPU payload by default. Callers
182
+ that deliberately retain ownership may pass `{ cleanup: false }` and keep the
183
+ returned displaced resource. If replacement cleanup fails, insertion throws a
184
+ contextual error and leaves the existing owner registered. These ordinary
185
+ ownership removals preserve the handle's last resource state; `PURGED` is
186
+ reserved for successful policy eviction through inactivity or byte pressure.
187
+
188
+ Canonical resource identity is the normalized source path plus its promised
189
+ output tag. `variant` is the explicit tag; otherwise `emit`, `requirement`, or
190
+ `payload` supplies it. Human-readable identities are written as
191
+ `res:/ship.gr2@cmf`, although MotherLode uses an internal delimiter. Reader,
192
+ constructor, and format-option implementations never enter the key. CjsLibrary
193
+ chooses the promised output and ResMan executes the current setup-time format
194
+ registration for it.
195
+
196
+ Output selection is case-insensitive for identity and matching, but format
197
+ readers receive the canonical declared spelling (for example `cmfJson`). A
198
+ legacy direct object loader exposes only its unforced default; named output
199
+ variants belong on a format class. Unsupported `@output` requests fail before
200
+ cache lookup, so a resident handle cannot bypass the declaration.
201
+
202
+ A released CPU payload retains only the small request needed to reconstruct
203
+ that same path/output from its source and `sourceRevision`. The retained
204
+ promised-output fields and source provenance win over later
205
+ `Ready()`/`GetObject()` overrides, while cache/reload policy remains per-call.
206
+ Payload leases protect active consumers; an engine may release its own backend
207
+ adapter without destroying shared CPU data. Change setup-time registrations
208
+ only with an explicit resource reset (`Delete`/`Clear`) or a new manager. A
209
+ changed output contract must use a new tag such as `@cmf2`.
210
+
211
+ `Startup()` and `Shutdown()` are idempotent. `HasKey`, `Lookup`, `Delete`,
212
+ `GetKeys`, `GetValues`, `GetSize`, `SetCacheSize`, `GetCacheSize`, `GetStats`,
213
+ `TrimCache`, `ReplaceExpected`, `Clear`, and `ClearCached` provide the
214
+ Carbon-shaped cache vocabulary plus the exact-owner compare-and-swap required
215
+ by staged JavaScript reload. The old `Has`, `GetCount`, and `DeleteAll` names
216
+ remain temporary compatibility aliases.
217
+
218
+ Byte budgeting applies only to records explicitly admitted with
219
+ `{ cached: true, bytes }`; JavaScript reachability is never inferred. The byte
220
+ value is a caller-supplied safe-integer eviction weight, not a heuristic walk of
221
+ the resource graph. `TrimCache()` removes positive-byte cached identities in
222
+ oldest-admission order until `cacheBytes <= cacheSize`. Live, locked,
223
+ `cacheable: false`, and zero-byte entries do not create pressure. With default
224
+ cleanup, successful pressure eviction performs the same deterministic
225
+ payload/adapter cleanup as inactivity eviction and marks detached compatible
226
+ handles `PURGED`.
227
+
228
+ `SetCacheSize()` installs and immediately enforces a new budget. `Update()` and
229
+ `Tick()` retry cache housekeeping after pumping queues; `{ cache: false }`
230
+ skips it for one update. Cleanup failure leaves that candidate canonical,
231
+ continues through later candidates, and throws
232
+ `CJS_MOTHERLODE_CACHE_TRIM_FAILED` with a partial result.
233
+
234
+ `CjsResMan` binds resource-facing `KeepAlive`, `KeepPayloadAlive`, `Lock`, and
235
+ `Unlock` operations to the canonical key. Publishing a non-null payload renews
236
+ its independent lease; `GetPayload()`, `HasPayload()`, `IsGood()`, and other
237
+ queries remain pure. `PurgeInactive(options)` performs an explicit deterministic
238
+ sweep using independent identity and payload frame/time limits. Locks skip both
239
+ forms of eviction. Identity expiry cleans adapters and payloads, detaches the
240
+ handle, marks it `PURGED`, and removes it; payload expiry releases only the CPU
241
+ payload. A sweep never fetches, prepares, or reloads a resource.
242
+
243
+ Generic/base reader results participate in the same payload ownership: the
244
+ manager stores the complete result through `SetPayload()` and mirrors it on the
245
+ compatibility `object` property. Payload release clears that alias only while
246
+ it still identifies the released value. Concurrent object/readiness calls
247
+ share only their in-flight operation; settled promises are removed so evicted
248
+ graphs are collectible and failed operations can be explicitly retried. A
249
+ resident payload returns without rereading, while a released payload is rebuilt
250
+ only by an explicit `GetObject()` or `Ready()` call.
251
+
252
+ Source and parsed-format caches use explicit provenance. `sourceRevision` is an
253
+ opaque caller/source-supplied string or finite number identifying source
254
+ content for one source object and normalized path. It scopes read caches only;
255
+ it does not alter MotherLode resource identity, and changing it does not replace a
256
+ resident payload without `reload: true`.
257
+
258
+ `cacheSource` and `cacheFormat` are tri-state per-call policies:
259
+
260
+ - omitted: share in-flight or explicitly retained work, then drop a newly
261
+ completed record;
262
+ - `true`: share and retain success; a joining caller upgrades the record;
263
+ - `false`: bypass sharing and retention.
264
+
265
+ Failures are never retained. Format records are additionally isolated by
266
+ selected source object, frozen registration descriptor, revision, and effective
267
+ format options. Re-registering a format with new defaults therefore cannot
268
+ reuse an old descriptor's parse. Registered defaults are copied into deeply
269
+ frozen plain-object/array snapshots. Material format options that cannot be
270
+ represented safely (for example class instances with hidden mutable state)
271
+ bypass format-cache sharing instead of risking a false match; functions and
272
+ byte views use cache-local identity plus visible byte content where applicable.
273
+
274
+ `reload: true` synchronously detaches every queued/source/format read record for
275
+ the selected source/path before fresh work starts. Existing consumers keep
276
+ their detached promises; reload does not abort them. Fresh success repopulates
277
+ only caches explicitly requested with `cacheSource: true` or
278
+ `cacheFormat: true`. `InvalidateReadCache(path, { source, sourceRevision })`
279
+ provides the same no-abort invalidation explicitly; omitting `sourceRevision`
280
+ removes all revisions for that source/path. `Delete()` remains canonical
281
+ resource-identity-only, while `Clear()` resets all read ledgers.
282
+
283
+ A resource loader retains the effective selected source and `sourceRevision`
284
+ for reconstruction, including the manager default selected at creation, but
285
+ not cache flags or one-shot reload.
286
+
287
+ Reload is candidate-first. When an owner already exists,
288
+ `GetResource(path, { reload: true })` returns a distinct off-registry candidate
289
+ without changing ordinary lookup. `Ready()` on that candidate, `GetObject()` /
290
+ `FetchResource()` with `reload: true`, and the explicit `ReloadObject()` /
291
+ `ReloadResource()` helpers all run the same queued contract:
292
+
293
+ 1. purge-lock the exact former owner and invalidate reusable reads once;
294
+ 2. read, convert through the selected format, and publish payload state only on the detached candidate;
295
+ 3. require the newest per-key reload token and exact former ownership;
296
+ 4. compare-and-swap the fully loaded CPU candidate into MotherLode;
297
+ 5. invalidate and clean the displaced handle after the lookup switch.
298
+
299
+ Source, format, or publication failure therefore leaves the former handle, state,
300
+ payload, and adapters canonical; the failed candidate's attached payload and
301
+ adapters are cleaned and its original error is retained. An otherwise-
302
+ successful candidate that was superseded, deleted, cleared, or replaced rejects
303
+ with `CJS_RESMAN_STALE_RELOAD_CANDIDATE` and cannot resurrect the key. Failed
304
+ freshness attempts still invalidate reusable source/format records when their
305
+ work begins; the already-published canonical payload is not dependent on those
306
+ records. If displaced-owner cleanup fails after the swap,
307
+ the promise rejects with `CJS_MOTHERLODE_REPLACE_CLEANUP_FAILED`, whose result
308
+ explicitly reports `committed: true`; the good candidate remains canonical.
309
+
310
+ This deliberately differs from Carbon's `BlueAsyncRes::Reload`, which reloads
311
+ one stable handle in place and releases its old data before success. Existing
312
+ JavaScript references likewise are never silently retargeted: they keep the
313
+ displaced handle, while fresh lookup sees the committed candidate.
314
+
315
+ Every queued, direct, standalone, and candidate resource preparation captures the exact
316
+ MotherLode, canonical key, resource handle, and manager-local ownership
317
+ generation. Delete, Clear, reload replacement, or handle reinsertion makes old
318
+ work stale before it can enter another state or publish. Otherwise-
319
+ successful obsolete work rejects with `CJS_RESMAN_STALE_RESOURCE_OPERATION`;
320
+ an obsolete source/format failure preserves its original rejection while
321
+ suppressing `SetError()` on the detached handle.
322
+
323
+ Candidate work is a normal `Wait()` root and blocks synchronous MotherLode
324
+ replacement while active. MotherLode replacement otherwise remains synchronous
325
+ configuration and rejects with
326
+ `CJS_RESMAN_ACTIVE_RESOURCE_OPERATIONS` while queued or direct mutations are
327
+ active. `Wait()` drains queued roots; callers must separately await direct load
328
+ or direct prepare promises before retrying replacement. Started source or
329
+ format work is not yet aborted; deterministic cleanup applies to the staged
330
+ candidate resource itself.
331
+
332
+ Automatic scheduling is available only when a caller supplies
333
+ `autoPurgePolicy` to the constructor/`Register()` or calls
334
+ `SetAutoPurgePolicy()`. It is disabled by default and deliberately accepts only
335
+ millisecond limits: MotherLode activity frames count explicit observations and
336
+ are not renderer frames. `Update()`/`Tick()` run a due sweep after queue pumps;
337
+ `{ purge: false }` skips it for one call. The first pump after configuration is
338
+ due immediately, then `intervalMilliseconds` limits cadence. Manager-owned
339
+ queued and direct resource work holds a balanced purge lock until completion.
340
+
341
+ ```js
342
+ const resMan = new CjsResMan({
343
+ source,
344
+ autoPurgePolicy: {
345
+ intervalMilliseconds: 1000,
346
+ maxIdleMilliseconds: 60_000,
347
+ payloadMaxIdleMilliseconds: 10_000
348
+ }
349
+ });
350
+
351
+ resMan.Update();
352
+ ```
353
+
354
+ Cache trimming and automatic inactivity sweeps retain the strict no-reload
355
+ rule. Application retention defaults, automatic resource/payload byte
356
+ estimation, separate CPU/adapter budgets, and purged-resource/device-loss
357
+ recovery policy remain later work.
358
+
359
+ ## Queued CPU load and publication
360
+
361
+ `GetObject()`, `LoadObject()`, and resource `Ready()` use two manager-owned
362
+ queues:
363
+
364
+ ```text
365
+ BACKGROUND: deduplicated source load, limited by maxConcurrentLoads
366
+ MAIN: reader/format conversion -> resource publication
367
+ ```
368
+
369
+ The main reader/format operation and publication are separate queue items.
370
+ `maxPrepareTime` is a per-pump budget in seconds, and
371
+ `maxPrepareItemsPerTick` can add an item-count limit. The default scheduler
372
+ keeps promise-based calls working; a `CjsLibrary` or direct caller can provide
373
+ its frame scheduler:
374
+
375
+ ```js
376
+ const resMan = new CjsResMan({
377
+ source,
378
+ maxConcurrentLoads: 8,
379
+ maxPrepareTime: 0.005,
380
+ queueScheduler: callback => requestAnimationFrame(callback)
381
+ });
382
+
383
+ await resMan.FetchResource("res:/model/ship.gr2", {
384
+ requirement: "geometry",
385
+ emit: "cmf"
386
+ });
387
+ ```
388
+
389
+ The selected format class owns conversion to the promised CPU output.
390
+ `CjsResMan` does not inspect WebGL, WebGPU, texture, geometry, or codec support,
391
+ and it does not run backend realization. An engine consumes the published CPU
392
+ resource afterward through its own explicit operation.
393
+
394
+ Blue-compatible queue controls are exposed directly on `CjsResMan`:
395
+ `AddToQueue`, `CancelFromQueue`, `GetNextIdForQueue`,
396
+ `PumpMainThreadQueue`, `PauseQueue`, `ResumeQueue`, `GetPendingLoads`, and
397
+ `GetPendingPrepares`. `Update()`/`Tick()` pump work. `Wait()` synchronously
398
+ captures queued resource-operation roots and low-level queue tasks that already
399
+ exist when it is called. Captured resource roots include publication work
400
+ enqueued after an asynchronous read; unrelated later roots/tasks do not
401
+ postpone the fence. Failure and queued cancellation count as settlement and remain
402
+ observable through their original operation promises.
403
+
404
+ By default `Wait()` pumps the two queues directly within their ordinary budgets
405
+ and never runs automatic purge housekeeping. It preserves pause state;
406
+ `{ pump: false }` leaves all progress to an external driver. A standalone
407
+ canonical `PrepareResourceObjectQueued()` call is a queued root. Direct
408
+ `LoadResourceObject()`, direct `PrepareResourceObject()`, standalone
409
+ `ReadResource()`, and standalone `ReadFormatOnce()` calls bypass both queues
410
+ and are outside this fence unless they own a captured queue task, although
411
+ direct resource mutations are still tracked for safe MotherLode replacement.
412
+ `WaitUrgent()` remains deferred until the queue has real per-item priority and
413
+ urgent-membership semantics.
414
+
415
+ Format classes own input extensions. Resource classes are registered by a
416
+ semantic requirement, never by file extension:
417
+
418
+ ```js
419
+ const resMan = new CjsResMan().Register({
420
+ source,
421
+ formats: [ CjsDdsFormat, CjsPngFormat ],
422
+ resourceTypes: [ TriTextureRes, Tr2ImageRes ]
423
+ });
424
+
425
+ const texture = resMan.GetResource("res:/image/ship.png", {
426
+ requirement: "texture",
427
+ emit: "image"
428
+ });
429
+ const image = resMan.GetResource("res:/image/ship.png", {
430
+ requirement: "image",
431
+ emit: "image"
432
+ });
433
+ ```
434
+
435
+ Those are distinct resource identities but share the normalized source-byte
436
+ operation. The manager does not expose an extension-to-resource compatibility
437
+ registry.
438
+
439
+ Texture-array resources expose one ordinary-looking proxy per ordered layer:
440
+
441
+ ```js
442
+ const textureArray = new CjsTextureArrayRes({
443
+ paths: [
444
+ "res:/detail1.dds",
445
+ "res:/detail2.dds",
446
+ "res:/detail3.dds"
447
+ ],
448
+ layerNames: [ "Detail1Map", "Detail2Map", "Detail3Map" ],
449
+ updateScheduler: resource => frameQueue.add(resource)
450
+ });
451
+
452
+ const detail2 = textureArray.GetLayerParameter(1);
453
+ detail2.SetValue("res:/replacement.dds");
454
+
455
+ detail2.textureRes === textureArray; // true
456
+ ```
457
+
458
+ Proxy setters only update their source path and invalidate the parent. The
459
+ parent is scheduled once even if several proxies change in the same frame.
460
+ The next-frame consumer calls `Update()` or `ConsumeUpdateRequest()` to obtain
461
+ one immutable ordered snapshot. Runtime-resource does not know which shader
462
+ metadata caused the aggregate request; shader packages and engine adapters map
463
+ public parameter names to layer indices.
464
+
465
+ Public effect parameters remain separate from these internal proxies. Their
466
+ authored paths and individual 2D source resources are not replaced by the
467
+ aggregate. An engine-owned, non-persisted bridge mirrors public changes into
468
+ the fixed internal layers.
469
+
470
+ Consumed snapshots are explicit in-flight generations. An adapter either
471
+ publishes the current candidate atomically, requeues retryable work, or records
472
+ failure:
473
+
474
+ ```js
475
+ const request = textureArray.ConsumeUpdateRequest();
476
+
477
+ try {
478
+ const candidate = await adapter.PrepareTextureArray(request);
479
+ const result = textureArray.CommitPreparedAdapterRevision(
480
+ request.revision,
481
+ "webgpu",
482
+ candidate
483
+ );
484
+
485
+ // A rejected/stale candidate is destroyed by the commit method by default.
486
+ // The adapter owns disposal of a successfully displaced allocation.
487
+ result.displaced?.destroy();
488
+ } catch (error) {
489
+ textureArray.FailUpdateRequest(request.revision, error, { retry: true });
490
+ }
491
+
492
+ await textureArray.Ready(); // the generation requested at call time
493
+ ```
494
+
495
+ `SetLayerResource()` attaches a resolved source without rewriting the logical
496
+ requested path. `TouchLayer()` invalidates an in-place source revision.
497
+ `RetryUpdateRequest()` restores consumed work, and `HandleAdapterLoss()` drops
498
+ an unusable adapter allocation and schedules a complete topology rebuild.
499
+ Topology-changing snapshots set `topologyChanged: true` and report only valid
500
+ current layer indices in `dirtyLayers`.
501
+
502
+ ## Development
503
+
504
+ Install dependencies and run the non-interactive baseline checks from the
505
+ repository root:
506
+
507
+ ```sh
508
+ npm install
509
+ npm run lint
510
+ npm run check
511
+ npm test
512
+ ```
513
+
514
+ `npm run check` builds the consumer package and proves that decorator metadata
515
+ matches between authoring source and built output. `npm test` additionally runs
516
+ the complete GPU-free unit suite; it requires no private assets, credentials,
517
+ network access, browser, or GPU after dependencies are installed.
518
+
519
+ See [Runtime Resource Lifecycle](resource-lifecycle.md) for state, retention,
520
+ and texture-array generation contracts. See
521
+ [Format ownership and fork provenance](FORMAT-PROVENANCE.md) for copied-reader
522
+ ownership, licenses, exclusions, and the deferred GR2 migration. Both documents
523
+ ship with the published package.
524
+
525
+ ## Provenance
526
+
527
+ CarbonEngine and Fenris Creations (CCP Games) are named for interoperability
528
+ and provenance context. This package contains CarbonEngineJS original resource
529
+ infrastructure, CarbonEngine-shaped resource ports, and maintained copies of
530
+ the non-shader readers identified in `FORMAT-PROVENANCE.md`. It does not copy
531
+ Fenris Creations game assets, proprietary documentation, or shader source.
532
+ CarbonEngine and historical JavaScript implementations were used as the
533
+ behavioral references described in the package notices.
534
+
535
+ This project is not affiliated with, endorsed by, or sponsored by CCP Games or
536
+ CCP ehf. EVE Online and related marks remain the property of their respective
537
+ owners.