@carbonenginejs/runtime-resource 0.8.0 → 0.9.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 (259) hide show
  1. package/LICENSE +21 -21
  2. package/NOTICE +29 -29
  3. package/README.md +67 -548
  4. package/dist/CjsMotherLode.js +1271 -1271
  5. package/dist/CjsResMan.js +3005 -3005
  6. package/dist/CjsResManQueue.js +214 -214
  7. package/dist/CjsResource.js +566 -566
  8. package/dist/CjsResourceSource.js +59 -59
  9. package/dist/_virtual/_rollupPluginBabelHelpers.js +153 -153
  10. package/dist/format/CjsBlueReader.js +269 -269
  11. package/dist/format/CjsFormat.js +192 -192
  12. package/dist/format/CjsReader.js +18 -18
  13. package/dist/format/CjsResourceProbe.js +277 -277
  14. package/dist/format/payloadContract.js +173 -173
  15. package/dist/formats/black/CjsBlackFormat.js +278 -278
  16. package/dist/formats/black/core/CjsBlackBinaryReader.js +153 -153
  17. package/dist/formats/black/core/CjsBlackPropertyReaders.js +382 -382
  18. package/dist/formats/black/core/CjsBlackReader.js +639 -639
  19. package/dist/formats/black/core/CjsBlackSchemaRegistry.js +433 -433
  20. package/dist/formats/black/core/black-schema-v1-2026-07-11.json.js +4 -4
  21. package/dist/formats/black/core/blackConstants.js +7 -7
  22. package/dist/formats/black/core/blackDefinitions.js +10 -10
  23. package/dist/formats/black/core/blackEnums.js +6 -6
  24. package/dist/formats/black/core/blackSchema.js +3 -3
  25. package/dist/formats/black/core/blackVersion.js +22 -22
  26. package/dist/formats/black/core/helpers.js +182 -182
  27. package/dist/formats/black/core/schema.js +4 -4
  28. package/dist/formats/black/index.js +2 -2
  29. package/dist/formats/bnk/CjsBnkFormat.js +125 -125
  30. package/dist/formats/bnk/core/graph.js +140 -140
  31. package/dist/formats/bnk/core/helpers.js +409 -409
  32. package/dist/formats/bnk/core/musicNodes.js +489 -489
  33. package/dist/formats/bnk/core/soundbanksInfo.js +246 -246
  34. package/dist/formats/bnk/index.js +2 -2
  35. package/dist/formats/cmf/CjsCmfFormat.js +497 -497
  36. package/dist/formats/cmf/core/binary.js +118 -118
  37. package/dist/formats/cmf/core/buffers.js +233 -233
  38. package/dist/formats/cmf/core/constants.js +47 -47
  39. package/dist/formats/cmf/core/gr2Anim.js +453 -453
  40. package/dist/formats/cmf/core/helpers.js +287 -287
  41. package/dist/formats/cmf/core/pack.js +276 -276
  42. package/dist/formats/cmf/core/schema.js +364 -364
  43. package/dist/formats/cmf/core/shared.js +268 -268
  44. package/dist/formats/cmf/core/writer.js +517 -517
  45. package/dist/formats/cmf/index.js +2 -2
  46. package/dist/formats/dds/CjsDdsFormat.js +193 -193
  47. package/dist/formats/dds/core/bc6h.js +288 -288
  48. package/dist/formats/dds/core/bc7.js +251 -251
  49. package/dist/formats/dds/core/helpers.js +815 -815
  50. package/dist/formats/dds/index.js +2 -2
  51. package/dist/formats/fbx/CjsFbxFormat.js +266 -266
  52. package/dist/formats/fbx/core/helpers.js +3901 -3901
  53. package/dist/formats/fbx/index.js +2 -2
  54. package/dist/formats/flac/CjsFlacFormat.js +87 -87
  55. package/dist/formats/flac/core/helpers.js +295 -295
  56. package/dist/formats/flac/index.js +2 -2
  57. package/dist/formats/gif/CjsGifFormat.js +87 -87
  58. package/dist/formats/gif/core/helpers.js +360 -360
  59. package/dist/formats/gif/index.js +2 -2
  60. package/dist/formats/gltf/CjsGltfFormat.js +290 -290
  61. package/dist/formats/gltf/core/helpers.js +292 -292
  62. package/dist/formats/gltf/core/json.js +79 -79
  63. package/dist/formats/gltf/core/parser.js +666 -666
  64. package/dist/formats/gltf/core/targets.js +163 -163
  65. package/dist/formats/gltf/index.js +2 -2
  66. package/dist/formats/gr2/CjsGr2Format.js +46 -0
  67. package/dist/formats/gr2/CjsGr2Format.js.map +1 -0
  68. package/dist/formats/gr2/core/CjsFormatGr2.js +273 -0
  69. package/dist/formats/gr2/core/CjsFormatGr2.js.map +1 -0
  70. package/dist/formats/gr2/core/bitknit2.js +280 -0
  71. package/dist/formats/gr2/core/bitknit2.js.map +1 -0
  72. package/dist/formats/gr2/core/curves.js +1047 -0
  73. package/dist/formats/gr2/core/curves.js.map +1 -0
  74. package/dist/formats/gr2/core/gsf.js +72 -0
  75. package/dist/formats/gr2/core/gsf.js.map +1 -0
  76. package/dist/formats/gr2/core/helpers.js +332 -0
  77. package/dist/formats/gr2/core/helpers.js.map +1 -0
  78. package/dist/formats/gr2/core/json.js +622 -0
  79. package/dist/formats/gr2/core/json.js.map +1 -0
  80. package/dist/formats/gr2/core/oodle1.js +388 -0
  81. package/dist/formats/gr2/core/oodle1.js.map +1 -0
  82. package/dist/formats/gr2/core/reader.js +617 -0
  83. package/dist/formats/gr2/core/reader.js.map +1 -0
  84. package/dist/formats/gr2/core/tangents.js +48 -0
  85. package/dist/formats/gr2/core/tangents.js.map +1 -0
  86. package/dist/formats/gr2/core/targets.js +351 -0
  87. package/dist/formats/gr2/core/targets.js.map +1 -0
  88. package/dist/formats/gr2/index.js +3 -0
  89. package/dist/formats/gr2/index.js.map +1 -0
  90. package/dist/formats/index.js +30 -23
  91. package/dist/formats/index.js.map +1 -1
  92. package/dist/formats/jpeg/CjsJpegFormat.js +206 -206
  93. package/dist/formats/jpeg/core/helpers.js +350 -350
  94. package/dist/formats/jpeg/core/jpeg.js +377 -377
  95. package/dist/formats/jpeg/index.js +2 -2
  96. package/dist/formats/mp3/CjsMp3Format.js +192 -192
  97. package/dist/formats/mp3/core/helpers.js +338 -338
  98. package/dist/formats/mp3/index.js +2 -2
  99. package/dist/formats/mp4/CjsMp4Format.js +191 -191
  100. package/dist/formats/mp4/core/helpers.js +449 -449
  101. package/dist/formats/mp4/index.js +2 -2
  102. package/dist/formats/obj/CjsObjFormat.js +253 -253
  103. package/dist/formats/obj/core/helpers.js +573 -573
  104. package/dist/formats/obj/core/json.js +64 -64
  105. package/dist/formats/obj/core/parser.js +321 -321
  106. package/dist/formats/obj/index.js +2 -2
  107. package/dist/formats/ogg/CjsOggFormat.js +88 -88
  108. package/dist/formats/ogg/core/helpers.js +387 -387
  109. package/dist/formats/ogg/core/imdct.js +178 -178
  110. package/dist/formats/ogg/core/vorbis.js +999 -999
  111. package/dist/formats/ogg/index.js +2 -2
  112. package/dist/formats/png/CjsPngFormat.js +195 -195
  113. package/dist/formats/png/core/helpers.js +583 -583
  114. package/dist/formats/png/index.js +2 -2
  115. package/dist/formats/red/CjsRedFormat.js +261 -261
  116. package/dist/formats/red/core/CjsRedReader.js +194 -194
  117. package/dist/formats/red/core/blackDefinitions.js +3 -3
  118. package/dist/formats/red/core/helpers.js +139 -139
  119. package/dist/formats/red/core/redGraph.js +68 -68
  120. package/dist/formats/red/core/schema.js +4 -4
  121. package/dist/formats/red/index.js +2 -2
  122. package/dist/formats/stl/CjsStlFormat.js +365 -365
  123. package/dist/formats/stl/core/helpers.js +261 -261
  124. package/dist/formats/stl/core/json.js +51 -51
  125. package/dist/formats/stl/core/stl.js +629 -629
  126. package/dist/formats/stl/core/targets.js +163 -163
  127. package/dist/formats/stl/index.js +2 -2
  128. package/dist/formats/tga/CjsTgaFormat.js +192 -192
  129. package/dist/formats/tga/core/helpers.js +446 -446
  130. package/dist/formats/tga/index.js +2 -2
  131. package/dist/formats/wav/CjsWavFormat.js +192 -192
  132. package/dist/formats/wav/core/helpers.js +328 -328
  133. package/dist/formats/wav/index.js +2 -2
  134. package/dist/formats/webm/CjsWebmFormat.js +191 -191
  135. package/dist/formats/webm/core/helpers.js +537 -537
  136. package/dist/formats/webm/index.js +2 -2
  137. package/dist/formats/webp/CjsWebpFormat.js +86 -86
  138. package/dist/formats/webp/core/helpers.js +214 -214
  139. package/dist/formats/webp/index.js +2 -2
  140. package/dist/formats/wem/CjsWemFormat.js +241 -241
  141. package/dist/formats/wem/core/bitStream.js +259 -259
  142. package/dist/formats/wem/core/codebookLibrary.js +164 -164
  143. package/dist/formats/wem/core/helpers.js +417 -417
  144. package/dist/formats/wem/core/packedCodebooksAotuv603.js +30 -30
  145. package/dist/formats/wem/core/ptadpcm.js +77 -77
  146. package/dist/formats/wem/core/resolve.js +121 -121
  147. package/dist/formats/wem/core/wemToOgg.js +485 -485
  148. package/dist/formats/wem/index.js +2 -2
  149. package/dist/formats/yaml/CjsYamlFormat.js +83 -83
  150. package/dist/formats/yaml/core/CjsYamlReader.js +305 -305
  151. package/dist/formats/yaml/core/helpers.js +160 -160
  152. package/dist/formats/yaml/index.js +2 -2
  153. package/dist/index.js +49 -48
  154. package/dist/index.js.map +1 -1
  155. package/dist/resourcePath.js +18 -18
  156. package/dist/resourceStates.js +9 -9
  157. package/dist/resources/AudioGeometryResData.js +47 -47
  158. package/dist/resources/GStateBindingCallbackData.js +31 -31
  159. package/dist/resources/MeshDecalData.js +37 -37
  160. package/dist/resources/MeshDecalLodData.js +34 -34
  161. package/dist/resources/Tr2EffectRes.js +71 -71
  162. package/dist/resources/Tr2GrannyIntersectionResult.js +60 -60
  163. package/dist/resources/Tr2GrannyStateRes.js +44 -44
  164. package/dist/resources/Tr2ImageRes.js +114 -114
  165. package/dist/resources/Tr2LightProfileRes.js +40 -40
  166. package/dist/resources/Tr2MaterialArea.js +34 -34
  167. package/dist/resources/Tr2MaterialMesh.js +31 -31
  168. package/dist/resources/Tr2MaterialRes.js +34 -34
  169. package/dist/resources/Tr2ShaderPermutation.js +43 -43
  170. package/dist/resources/Tr2TextureLodManager.js +80 -80
  171. package/dist/resources/Tr2TextureLodUpdateRequest.js +37 -37
  172. package/dist/resources/Tr2TexturePackChannel.js +37 -37
  173. package/dist/resources/Tr2TexturePipeline.js +52 -52
  174. package/dist/resources/Tr2TexturePipelineParams.js +34 -34
  175. package/dist/resources/Tr2TexturePipelineStepCompress.js +40 -40
  176. package/dist/resources/Tr2TexturePipelineStepGenerateMips.js +22 -0
  177. package/dist/resources/Tr2TexturePipelineStepGenerateMips.js.map +1 -0
  178. package/dist/resources/Tr2TexturePipelineStepLimitSize.js +34 -34
  179. package/dist/resources/Tr2TexturePipelineStepLoad.js +31 -31
  180. package/dist/resources/Tr2TexturePipelineStepPack.js +43 -43
  181. package/dist/resources/TriGeometryRes.js +239 -239
  182. package/dist/resources/TriGeometryResAreaData.js +59 -59
  183. package/dist/resources/TriGeometryResJointData.js +38 -38
  184. package/dist/resources/TriGeometryResLodData.js +88 -88
  185. package/dist/resources/TriGeometryResMeshData.js +63 -63
  186. package/dist/resources/TriGeometryResSkeletonData.js +34 -34
  187. package/dist/resources/TriGrannyRes.js +43 -43
  188. package/dist/resources/TriJointBinding.js +38 -38
  189. package/dist/resources/TriMorphTargetGeometryConstants.js +46 -46
  190. package/dist/resources/TriRtGeometryConstants.js +88 -88
  191. package/dist/resources/TriTextureRes.js +304 -304
  192. package/dist/resources/enums.js +18 -18
  193. package/dist/resources/resourceBoundary.js +47 -47
  194. package/dist/resources/texturePipelineBehavior.js +308 -308
  195. package/dist/texture/CjsTextureArrayRes.js +406 -406
  196. package/dist/texture/CjsTextureParameterProxy.js +133 -133
  197. package/docs/README.md +69 -0
  198. package/docs/architecture.md +86 -0
  199. package/docs/concepts/resource-lifecycle.md +217 -0
  200. package/docs/formats/README.md +104 -0
  201. package/{FORMAT-PROVENANCE.md → docs/formats/provenance.md} +173 -155
  202. package/docs/formats/stl.md +37 -0
  203. package/docs/formats/wwise.md +44 -0
  204. package/docs/reference/events.md +92 -0
  205. package/docs/reference/motherlode-cache.md +244 -0
  206. package/docs/reference/queues.md +102 -0
  207. package/docs/reference/reload.md +107 -0
  208. package/docs/reference/texture-arrays.md +113 -0
  209. package/docs/reference/texture-pipeline.md +53 -0
  210. package/docs/roadmap.md +104 -0
  211. package/format-notices/black/LICENSE +21 -21
  212. package/format-notices/black/NOTICE +47 -47
  213. package/format-notices/bnk/LICENSE +21 -21
  214. package/format-notices/bnk/NOTICE +20 -20
  215. package/format-notices/cmf/LICENSE +21 -21
  216. package/format-notices/cmf/NOTICE +36 -36
  217. package/format-notices/dds/LICENSE +21 -21
  218. package/format-notices/dds/NOTICE +14 -14
  219. package/format-notices/fbx/LICENSE +21 -21
  220. package/format-notices/fbx/NOTICE +14 -14
  221. package/format-notices/flac/LICENSE +21 -21
  222. package/format-notices/flac/NOTICE +14 -14
  223. package/format-notices/gif/LICENSE +21 -21
  224. package/format-notices/gif/NOTICE +14 -14
  225. package/format-notices/gltf/LICENSE +21 -21
  226. package/format-notices/gltf/NOTICE +27 -27
  227. package/format-notices/gr2/LICENSE +21 -0
  228. package/format-notices/gr2/NOTICE +60 -0
  229. package/format-notices/gr2/THIRD-PARTY-NOTICES.md +93 -0
  230. package/format-notices/jpeg/LICENSE +21 -21
  231. package/format-notices/jpeg/NOTICE +14 -14
  232. package/format-notices/mp3/LICENSE +21 -21
  233. package/format-notices/mp3/NOTICE +14 -14
  234. package/format-notices/mp4/LICENSE +21 -21
  235. package/format-notices/mp4/NOTICE +14 -14
  236. package/format-notices/obj/LICENSE +21 -21
  237. package/format-notices/obj/NOTICE +26 -26
  238. package/format-notices/ogg/LICENSE +21 -21
  239. package/format-notices/ogg/NOTICE +28 -28
  240. package/format-notices/png/LICENSE +21 -21
  241. package/format-notices/png/NOTICE +14 -14
  242. package/format-notices/red/LICENSE +21 -21
  243. package/format-notices/red/NOTICE +31 -31
  244. package/format-notices/stl/LICENSE +21 -21
  245. package/format-notices/stl/NOTICE +21 -21
  246. package/format-notices/tga/LICENSE +21 -21
  247. package/format-notices/tga/NOTICE +14 -14
  248. package/format-notices/wav/LICENSE +21 -21
  249. package/format-notices/wav/NOTICE +14 -14
  250. package/format-notices/webm/LICENSE +21 -21
  251. package/format-notices/webm/NOTICE +14 -14
  252. package/format-notices/webp/LICENSE +21 -21
  253. package/format-notices/webp/NOTICE +14 -14
  254. package/format-notices/wem/LICENSE +57 -57
  255. package/format-notices/wem/NOTICE +33 -33
  256. package/format-notices/yaml/LICENSE +21 -21
  257. package/format-notices/yaml/NOTICE +44 -44
  258. package/package.json +52 -51
  259. package/resource-lifecycle.md +0 -679
@@ -0,0 +1,92 @@
1
+ # Event emitter contract
2
+
3
+ Status: Evolving
4
+ Scope: `@carbonenginejs/runtime-resource`
5
+ Audience: Users and integrators
6
+ Summary: Defines the `CjsEventEmitter` API surface used by resource managers and the memory rules that keep listeners from leaking graphs.
7
+
8
+ ## Purpose
9
+
10
+ `CjsEventEmitter` (from `core-types/model`) is a separate base class so
11
+ non-model runtime services can emit events without extending `CjsModel`;
12
+ `CjsResMan` uses that path. CarbonEngineJS exposes one small event-emitter API
13
+ and avoids short generic names such as `On`, `Once`, `Off`, and `Emit` on
14
+ Carbon-shaped classes. There is no separate resource notification/callback
15
+ compatibility layer beside events, and no listener scopes, subscription
16
+ handles, or owner-side `ListenTo()` helpers.
17
+
18
+ ## API
19
+
20
+ - `AddEvents(events)`
21
+ - `OnEvent(eventName, listener, source?)`
22
+ - `OnceEvent(eventName, listener, source?)`
23
+ - `OffEvent(eventName = "*", listener?, source?)`
24
+ - `EmitEvent(eventName, ...args)`
25
+ - `HasEvent(eventName = "*", listener?, source?)`
26
+ - `ClearEvent(eventName = "*")`
27
+ - `GetEventNames()` and `GetEventListenerCount(eventName = "*")`
28
+
29
+ The optional `source` is the callback's `this` value and an explicit matching
30
+ identity for removal. Mutating event methods return the emitter for chaining.
31
+ Event names are normalized to lowercase and dispatched by exact match.
32
+ Wildcard names are accepted only by lookup and cleanup methods; there is no
33
+ wildcard listener dispatch, no `family.event` or ancestor routing, and no
34
+ event payload history. Resource classes may emit their own state or domain
35
+ events, but the emitter does not invent a resource lifecycle contract.
36
+
37
+ ## Memory rules
38
+
39
+ Event storage is the optional `events` member of the emitter's non-enumerable
40
+ `__state` object:
41
+
42
+ ```text
43
+ emitter.__state (non-enumerable, allocated only when some subsystem needs it)
44
+ events -> eventName -> Set<listenerRecord>
45
+
46
+ listenerRecord
47
+ emitter
48
+ eventName
49
+ listener
50
+ source
51
+ once
52
+ ```
53
+
54
+ The event map is created only when the first listener is registered and
55
+ deleted when the last record is removed. That does not make listeners weak:
56
+ as long as an emitter is reachable, its event map strongly references
57
+ listener functions and sources, and those listeners can keep whole
58
+ scene/resource graphs alive. The contract is therefore:
59
+
60
+ - `OnceEvent()` removes the listener on first dispatch, even if the callback
61
+ throws.
62
+ - `OffEvent(eventName, listener, source)` removes the exact listener/source
63
+ entry; `target.OffEvent("*", null, source)` removes all of a source's
64
+ records. An external party that no longer observes a target must call
65
+ `OffEvent()`.
66
+ - `Unload()` and `Purge()` are resource state/cache operations, not an
67
+ implied listener-destruction lifecycle.
68
+ - `OnEvent()` returns the emitter; it does not return unsubscribe closures,
69
+ because those closures create another reference path.
70
+ - Deterministic cleanup is the contract; `WeakRef`/`FinalizationRegistry` may
71
+ help diagnostics but are not a lifecycle mechanism.
72
+
73
+ Multiple listeners on the same event are allowed because each event bucket is
74
+ a set of records. A raw `CjsEventEmitter` does not gain model-owned `dirty`
75
+ or `rebuild` state. The target is "easy to debug, hard to leak": clear
76
+ ownership of who subscribed, who unsubscribes, and which cleanup phase clears
77
+ remaining listeners.
78
+
79
+ ## Relationship to model dirty state
80
+
81
+ `CjsModel` has dirty-state helpers (`MarkDirty`, `ClearDirty`, `ConsumeDirty`,
82
+ `GetDirtyNotifications`) for model invalidation. `SetValues()` compares
83
+ incoming values with the current field values and only marks dirty when a
84
+ value actually changes. A plain `MarkDirty()` means broad dirty invalidation;
85
+ it does not request a rebuild, and deferred rebuild reasons belong to the
86
+ independent `model.__state.rebuild` set. That model machinery is not a
87
+ resource lifecycle event system; resource lifecycle events remain a
88
+ resource/resman concern.
89
+
90
+ ## Related documentation
91
+
92
+ - [MotherLode identity, cache, and retention](motherlode-cache.md)
@@ -0,0 +1,244 @@
1
+ # MotherLode identity, cache, and retention
2
+
3
+ Status: Evolving
4
+ Scope: `@carbonenginejs/runtime-resource`
5
+ Audience: Users and integrators
6
+ Summary: Defines canonical resource identity, ownership and replacement, the recorded-byte cache, payload retention, read-cache provenance, and purge contracts.
7
+
8
+ ## Canonical identity
9
+
10
+ Canonical resource identity is the normalized source path plus its promised
11
+ output tag. `variant` is the explicit tag; otherwise `emit`, `requirement`, or
12
+ `payload` supplies it. Human-readable identities are written as
13
+ `res:/ship.gr2@cmf`, although MotherLode uses an internal delimiter. Reader,
14
+ constructor, and format-option implementations never enter the key. CjsLibrary
15
+ chooses the promised output and ResMan executes the current setup-time format
16
+ registration for it.
17
+
18
+ Output selection is case-insensitive for identity and matching, but format
19
+ readers receive the canonical declared spelling (for example `cmfJson`). A
20
+ legacy direct object loader exposes only its unforced default; named output
21
+ variants belong on a format class. Unsupported `@output` requests fail before
22
+ cache lookup, so a resident handle cannot bypass the declaration.
23
+
24
+ The selected constructor, reader, and format defaults/options are setup-time
25
+ execution details, not MotherLode identity. A changed registration does not
26
+ create a hidden second resource; reset the affected identity
27
+ (`Delete`/`Clear`) or create a new manager. A changed output contract must use
28
+ a new tag such as `@cmf2`.
29
+
30
+ ## Ownership and replacement
31
+
32
+ `CjsResMan` resolves each normalized path and promised output to one canonical
33
+ MotherLode key. `Insert(key, resource, options)` reports `{ inserted,
34
+ replaced, displaced }`; replacement, deletion, clearing, and shutdown destroy
35
+ attached adapter allocations and release the complete CPU payload by default.
36
+ Callers that deliberately retain ownership may pass `{ cleanup: false }` and
37
+ keep the returned displaced resource. If replacement cleanup fails, insertion
38
+ throws a contextual error and leaves the existing owner registered. These
39
+ ordinary ownership removals preserve the handle's last resource state;
40
+ `PURGED` is reserved for successful policy eviction through inactivity or
41
+ byte pressure.
42
+
43
+ `Startup()` and `Shutdown()` are idempotent. `HasKey`, `Lookup`, `Delete`,
44
+ `GetKeys`, `GetValues`, `GetSize`, `SetCacheSize`, `GetCacheSize`, `GetStats`,
45
+ `TrimCache`, `ReplaceExpected`, `Clear`, and `ClearCached` provide the
46
+ Carbon-shaped cache vocabulary plus the exact-owner compare-and-swap required
47
+ by staged JavaScript reload. The old `Has`, `GetCount`, and `DeleteAll` names
48
+ remain temporary compatibility aliases.
49
+
50
+ ## Recorded-byte cache
51
+
52
+ Carbon can infer when only its cache retains a resource through
53
+ weak-reference and refcount transitions. JavaScript cannot reproduce that
54
+ ownership test reliably, so `CjsMotherLode` budgets only entries that a caller
55
+ explicitly classifies with `{ cached: true, bytes }`; JavaScript reachability
56
+ is never inferred. The byte value is an exact caller-supplied safe-integer
57
+ eviction weight, not a heuristic walk of the resource graph; runtime-resource
58
+ does not walk arbitrary cyclic/shared object graphs or invoke payload getters
59
+ to guess size.
60
+
61
+ Explicit cached entries receive a monotonic admission sequence. With default
62
+ cleanup, `TrimCache(options)` destroys adapters, releases payloads, detaches
63
+ lifecycle callbacks, marks compatible handles `PURGED`, and removes
64
+ positive-byte cached identities in oldest-admission order until
65
+ `cacheBytes <= cacheSize`. Live, locked, `cacheable: false`, and zero-byte
66
+ entries do not create pressure. `KeepAlive()` and `Lock()` promote a cached
67
+ record to live; `Unlock()` does not silently re-admit it.
68
+
69
+ `SetCacheSize(bytes, options)` installs and immediately enforces the new
70
+ budget. `CjsResMan.Update()` and `Tick()` retry cache housekeeping after
71
+ pumping queues; `{ cache: false }` skips it for one update without changing
72
+ policy. Cleanup is transactional per identity: a failed candidate remains
73
+ canonical, later candidates are still attempted, and the aggregate
74
+ `CJS_MOTHERLODE_CACHE_TRIM_FAILED` error carries successful evictions and any
75
+ remaining over-budget state. Trimming never reads, prepares, or reloads data.
76
+
77
+ ## Payload retention
78
+
79
+ Reader and converter outputs are plain transient payload objects, not resource
80
+ classes or DTO models. A payload may contain more decoded data than a
81
+ particular resource or engine adapter needs. Each concrete resource validates
82
+ the fields it requires before publishing the payload and retains the scalars
83
+ and references it needs. An adapter may retain additional references in
84
+ adapter-owned state. Referencing payload-owned typed arrays is valid and
85
+ preferable to copying them merely to change ownership.
86
+
87
+ The lifecycle treats resource residency and payload residency independently:
88
+
89
+ ```text
90
+ resource.KeepAlive()
91
+ -> renew resource/cache residency
92
+
93
+ resource.KeepPayloadAlive()
94
+ -> renew the attached payload lease
95
+
96
+ resource.ReleasePayload()
97
+ -> explicitly release the full payload reference
98
+ ```
99
+
100
+ `CjsResMan` binds resource-facing `KeepAlive()`, `KeepPayloadAlive()`,
101
+ `Lock()`, and `Unlock()` to the resource's canonical MotherLode key.
102
+ `SetPayload()` renews both identity and payload activity when it publishes a
103
+ non-null payload. `GetPayload()`, `HasPayload()`, `IsGood()`, and other
104
+ queries are pure; reading the payload does not implicitly renew its lease.
105
+ Detached and purged handles retain deterministic no-op liveness methods rather
106
+ than silently starting work.
107
+
108
+ A released CPU payload retains only the small request needed to reconstruct
109
+ that same path/output from its source and `sourceRevision`. The retained
110
+ promised-output fields and source provenance win over later
111
+ `Ready()`/`GetObject()` overrides, while cache/reload policy remains per-call.
112
+ Payload leases protect active consumers; an engine may release its own backend
113
+ adapter without destroying shared CPU data.
114
+
115
+ Both semantic and generic/base resource results use the payload slot: the
116
+ manager stores the complete result through `SetPayload()` and mirrors it on
117
+ the compatibility `object` property for base resources. Payload release clears
118
+ that alias only while it still identifies the released value. Semantic
119
+ resources continue to expose `object === resource` while holding their
120
+ validated plain payload privately.
121
+
122
+ Object-operation promises are retained only while in flight. Concurrent
123
+ `GetObject()`/`Ready()` calls share one operation; a resident result is
124
+ returned without source work and renews the explicit payload lease.
125
+ Settlement removes the operation record so its result graph can be reclaimed
126
+ and a failure can be retried. After payload release, only a new explicit
127
+ object/readiness call reconstructs it; queries, lease calls, and purge sweeps
128
+ do not.
129
+
130
+ The processor preparing a resource decides when the full payload can be
131
+ released:
132
+
133
+ ```text
134
+ format reader -> plain payload -> resource validation + adapter prepare
135
+ |
136
+ +-> resource retains required values/references
137
+ +-> adapter retains adapter-specific state/references
138
+ +-> release payload after successful preparation
139
+ `-> or renew its lease for deferred/further work
140
+ ```
141
+
142
+ A time- or frame-based lease is a fallback against abandoned payloads. An
143
+ owner performing deferred work can renew the lease. If an expired payload is
144
+ required again, the caller must explicitly request reconstruction; lease
145
+ renewal and purging never fetch or reload source data. Dynamic or
146
+ non-reloadable resources must remain locked, retain the required payload, or
147
+ be able to recreate it.
148
+
149
+ Payload references are shared read-only by default. Preparing WebGL and
150
+ WebGPU adapters side by side should normally pass the same payload to both
151
+ consumers and retain it until both have finished. Copying is an explicit
152
+ consumer operation, justified when a consumer must mutate data, transfer and
153
+ detach an `ArrayBuffer`, or retain an independently writable snapshot. The
154
+ consumer should copy only the fields it requires; runtime-resource does not
155
+ automatically deep-clone payload or typed-array bundles.
156
+
157
+ ## Read-cache provenance
158
+
159
+ Source and parsed-format caches use explicit provenance, separate from
160
+ path/output resource identity. `sourceRevision` is an opaque caller/source-
161
+ supplied string or finite number identifying source content for one source
162
+ object and normalized path. It scopes read caches only; it does not alter
163
+ MotherLode resource identity, and changing it does not replace a resident
164
+ payload without `reload: true`. Source and format records do not share across
165
+ revisions.
166
+
167
+ `cacheSource` and `cacheFormat` are tri-state per-call policies:
168
+
169
+ - omitted: share in-flight or explicitly retained work, then drop a newly
170
+ completed record;
171
+ - `true`: share and retain success; a joining caller upgrades the record;
172
+ - `false`: bypass sharing and retention.
173
+
174
+ Failures are never retained. Format records are additionally isolated by
175
+ selected source object, frozen registration descriptor, revision, and
176
+ effective format options, so another source or a re-registered default cannot
177
+ reuse a stale parse. Re-registering a format with new defaults therefore
178
+ cannot reuse an old descriptor's parse. Registered defaults are copied into
179
+ deeply frozen plain-object/array snapshots. Material format options that
180
+ cannot be represented safely (for example class instances with hidden mutable
181
+ state) bypass format-cache sharing instead of risking a false match;
182
+ functions and byte views use cache-local identity plus visible byte content
183
+ where applicable.
184
+
185
+ A resource loader retains the effective selected source and `sourceRevision`
186
+ for reconstruction, including the manager default selected at creation, but
187
+ not cache flags or one-shot reload.
188
+
189
+ ## Explicit and automatic purging
190
+
191
+ `PurgeInactive(options)` performs an explicit deterministic sweep using
192
+ independent identity and payload frame/time limits. Locks skip both forms of
193
+ eviction. Identity expiry destroys adapter resources, releases the payload,
194
+ detaches lifecycle callbacks, marks compatible handles `PURGED`, and removes
195
+ the canonical key; payload expiry calls `ReleasePayload()` while retaining
196
+ identity and adapter allocations. Candidate failures are aggregated after the
197
+ sweep has continued over other entries. A sweep never fetches, prepares, or
198
+ reloads a resource.
199
+
200
+ Automatic scheduling is available only when a caller supplies
201
+ `autoPurgePolicy` to the constructor/`Register()` or calls
202
+ `SetAutoPurgePolicy()`. It is disabled by default and deliberately accepts
203
+ only millisecond limits: MotherLode activity frames count explicit
204
+ observations and are not renderer frames. A policy must set at least one of
205
+ `maxIdleMilliseconds` or `payloadMaxIdleMilliseconds`;
206
+ `intervalMilliseconds` defaults to 1000. The first
207
+ `PumpAutoPurge()`/`Update()` after configuration sweeps immediately, then the
208
+ interval sets the minimum cadence. `Update({ purge: false })` suppresses a
209
+ sweep for one update without changing cadence. A regressing clock rebases and
210
+ skips one pump; custom deterministic clocks should be shared with MotherLode.
211
+ Recorded-byte cache trimming is separate from this opt-in inactivity policy
212
+ and runs on ordinary updates unless `{ cache: false }` is supplied.
213
+
214
+ ```js
215
+ const resMan = new CjsResMan({
216
+ source,
217
+ autoPurgePolicy: {
218
+ intervalMilliseconds: 1000,
219
+ maxIdleMilliseconds: 60_000,
220
+ payloadMaxIdleMilliseconds: 10_000
221
+ }
222
+ });
223
+
224
+ resMan.Update();
225
+ ```
226
+
227
+ Both queued `QueueResourceObject()` work and direct `LoadResourceObject()`
228
+ work hold one manager-owned lock from request/loading publication through
229
+ success or failure. The lock is balanced independently of caller locks, so
230
+ automatic or manual sweeps cannot detach a handle while its read/prepare
231
+ operation is still active. Lock release is conditional on the same captured
232
+ ownership generation, so stale work cannot decrement a newly rebound handle's
233
+ lock. Scheduling and active-work protection do not fetch or reload data.
234
+
235
+ Cache trimming and automatic inactivity sweeps retain the strict no-reload
236
+ rule. Application retention defaults, automatic resource/payload byte
237
+ estimation, separate CPU/adapter budgets, and purged-resource/device-loss
238
+ recovery policy are future work; see the [roadmap](../roadmap.md).
239
+
240
+ ## Related documentation
241
+
242
+ - [Resource lifecycle concepts](../concepts/resource-lifecycle.md)
243
+ - [Candidate-first atomic reload](../reference/reload.md)
244
+ - [Queues and the Wait fence](../reference/queues.md)
@@ -0,0 +1,102 @@
1
+ # Queues, publication, and the Wait fence
2
+
3
+ Status: Evolving
4
+ Scope: `@carbonenginejs/runtime-resource`
5
+ Audience: Users and integrators
6
+ Summary: Defines the two manager-owned queues, publication budgeting, registration, and the `Wait()` fence semantics.
7
+
8
+ ## Queued CPU load and publication
9
+
10
+ `GetObject()`, `LoadObject()`, and resource `Ready()` use two manager-owned
11
+ queues:
12
+
13
+ ```text
14
+ BACKGROUND: deduplicated source load, limited by maxConcurrentLoads
15
+ MAIN: reader/format conversion -> resource publication
16
+ ```
17
+
18
+ The main reader/format operation and publication are separate queue items.
19
+ `maxPrepareTime` is a per-pump budget in seconds limiting synchronous
20
+ main-queue work started in one pump, and `maxPrepareItemsPerTick` can add an
21
+ item-count limit. Promise-returning format work remains in flight without
22
+ blocking the JavaScript event loop. The default scheduler keeps promise-based
23
+ calls working; a `CjsLibrary` or direct caller can provide its frame
24
+ scheduler:
25
+
26
+ ```js
27
+ const resMan = new CjsResMan({
28
+ source,
29
+ maxConcurrentLoads: 8,
30
+ maxPrepareTime: 0.005,
31
+ queueScheduler: callback => requestAnimationFrame(callback)
32
+ });
33
+
34
+ await resMan.FetchResource("res:/model/ship.gr2", {
35
+ requirement: "geometry",
36
+ emit: "cmf"
37
+ });
38
+ ```
39
+
40
+ The selected format class owns conversion to the promised CPU output.
41
+ `CjsResMan` does not inspect WebGL, WebGPU, texture, geometry, or codec
42
+ support, and it does not run backend realization. An engine consumes the
43
+ published CPU resource afterward through its own explicit operation.
44
+
45
+ ## Queue controls
46
+
47
+ Blue-compatible queue controls are exposed directly on `CjsResMan`:
48
+ `AddToQueue`, `CancelFromQueue`, `GetNextIdForQueue`, `PumpMainThreadQueue`,
49
+ `PauseQueue`, `ResumeQueue`, `GetPendingLoads`, and `GetPendingPrepares`.
50
+ `Update()`/`Tick()` pump work.
51
+
52
+ ## The Wait fence
53
+
54
+ `Wait()` synchronously captures queued resource-operation roots and low-level
55
+ queue tasks that already exist when it is called. Captured resource roots
56
+ include publication work enqueued after an asynchronous read; unrelated later
57
+ roots/tasks do not postpone the fence. Failure and queued cancellation count
58
+ as settlement, remain observable through their original operation promises,
59
+ and cross the fence without making `Wait()` reject.
60
+
61
+ By default `Wait()` pumps the two queues directly within their ordinary
62
+ budgets and never runs automatic purge housekeeping. It preserves pause
63
+ state; `{ pump: false }` leaves all progress to an external driver. A
64
+ standalone canonical `PrepareResourceObjectQueued()` call is a queued root.
65
+ Direct `LoadResourceObject()`, direct `PrepareResourceObject()`, standalone
66
+ `ReadResource()`, and standalone `ReadFormatOnce()` calls bypass both queues
67
+ and are outside this fence unless they own a captured queue task, although
68
+ direct resource mutations are still tracked for safe MotherLode replacement.
69
+ `WaitUrgent()` remains deferred until the queue has real per-item priority
70
+ and urgent-membership semantics.
71
+
72
+ ## Registration
73
+
74
+ Format classes own input extensions. Resource classes are registered by a
75
+ semantic requirement, never by file extension:
76
+
77
+ ```js
78
+ const resMan = new CjsResMan().Register({
79
+ source,
80
+ formats: [ CjsDdsFormat, CjsPngFormat ],
81
+ resourceTypes: [ TriTextureRes, Tr2ImageRes ]
82
+ });
83
+
84
+ const texture = resMan.GetResource("res:/image/ship.png", {
85
+ requirement: "texture",
86
+ emit: "image"
87
+ });
88
+ const image = resMan.GetResource("res:/image/ship.png", {
89
+ requirement: "image",
90
+ emit: "image"
91
+ });
92
+ ```
93
+
94
+ Those are distinct resource identities but share the normalized source-byte
95
+ operation. The manager does not expose an extension-to-resource compatibility
96
+ registry.
97
+
98
+ ## Related documentation
99
+
100
+ - [Resource lifecycle concepts](../concepts/resource-lifecycle.md)
101
+ - [MotherLode identity, cache, and retention](motherlode-cache.md)
102
+ - [Format subpaths](../formats/README.md)
@@ -0,0 +1,107 @@
1
+ # Candidate-first atomic reload
2
+
3
+ Status: Evolving
4
+ Scope: `@carbonenginejs/runtime-resource`
5
+ Audience: Users and integrators
6
+ Summary: Defines the reload contract that keeps the last published good handle canonical until a distinct candidate has fully succeeded.
7
+
8
+ ## Contract
9
+
10
+ Reload is candidate-first. When an owner already exists,
11
+ `GetResource(path, { reload: true })` constructs and initializes a distinct
12
+ off-registry candidate without changing ordinary lookup. Calling `Ready()` on
13
+ that candidate, using `GetObject()` / `FetchResource()` with `reload: true`,
14
+ or calling the explicit `ReloadObject()` / `ReloadResource()` helpers starts
15
+ one shared candidate operation that runs the same queued contract:
16
+
17
+ 1. purge-lock the exact former owner and invalidate reusable reads once;
18
+ 2. read, convert through the selected format, and publish payload state only
19
+ on the detached candidate;
20
+ 3. require the newest per-key reload token and exact former ownership;
21
+ 4. compare-and-swap the fully loaded CPU candidate into MotherLode;
22
+ 5. invalidate and clean the displaced handle after the lookup switch.
23
+
24
+ The manager captures the exact MotherLode, key, former handle, former
25
+ ownership generation, and a newest-request token. Reader, prepare, and
26
+ publication stages mutate only the detached candidate and validate candidate
27
+ authority before and after asynchronous boundaries. A fully loaded CPU
28
+ candidate commits through `CjsMotherLode.ReplaceExpected()` only if the exact
29
+ former owner and newest token still match. The final authority callback and
30
+ exact-record check run immediately before the synchronous map switch, with no
31
+ user cleanup or `await` between the comparison and publication.
32
+
33
+ After the switch, the displaced ownership generation is invalidated, the
34
+ candidate receives ordinary lifecycle/reconstruction callbacks, and the former
35
+ handle is cleaned exactly once. Existing JavaScript references are not
36
+ retargeted; they keep the displaced handle, while fresh lookup sees the
37
+ committed candidate.
38
+
39
+ ## Failure behavior
40
+
41
+ Source, format, or publication failure leaves the former handle, state,
42
+ payload, and adapters canonical; the failed candidate's attached payload and
43
+ adapters are cleaned and its original error is retained. An otherwise-
44
+ successful candidate that was superseded, deleted, cleared, or replaced
45
+ rejects with `CJS_RESMAN_STALE_RELOAD_CANDIDATE` and cannot resurrect the
46
+ key. Constructors that return the former singleton are rejected before
47
+ `Initialize()` can mutate it because staging requires a distinct handle.
48
+
49
+ Failed freshness attempts still invalidate reusable source/format records
50
+ when their work begins; the already-published canonical payload is not
51
+ dependent on those records.
52
+
53
+ Cleanup errors have explicit sides. Candidate cleanup failure aggregates with
54
+ the original preparation/stale error as
55
+ `CJS_RESMAN_RELOAD_CANDIDATE_CLEANUP_FAILED` while the former owner remains
56
+ canonical. A displaced-owner cleanup failure occurs after publication and
57
+ rejects as `CJS_MOTHERLODE_REPLACE_CLEANUP_FAILED`, whose result explicitly
58
+ reports `committed: true`; the already-good candidate remains canonical.
59
+
60
+ ## Read-cache interaction
61
+
62
+ `reload: true` synchronously detaches every queued/source/format read record
63
+ for the selected source/path before fresh work starts. Existing consumers
64
+ keep their detached promises; reload does not abort them. Fresh success
65
+ repopulates only caches explicitly requested with `cacheSource: true` or
66
+ `cacheFormat: true`. `InvalidateReadCache(path, { source, sourceRevision })`
67
+ provides the same no-abort invalidation explicitly; omitting
68
+ `sourceRevision` removes all revisions for that source/path. `Delete()`
69
+ remains canonical resource-identity-only, while `Clear()` resets all read
70
+ ledgers. A failed reload preserves the former payload but does not restore
71
+ reusable read-cache entries detached by its explicit freshness request.
72
+
73
+ ## Publication authority and staleness
74
+
75
+ Every queued, direct, standalone, and candidate resource preparation captures
76
+ an immutable publication authority: the exact MotherLode, canonical key,
77
+ resource handle, and a manager-local ownership generation. The manager
78
+ validates that authority before and after state changes, asynchronous
79
+ reader/format work, and publication. Delete, Clear, successful reload commit,
80
+ or exact-handle reinsertion therefore makes older work reject with
81
+ `CJS_RESMAN_STALE_RESOURCE_OPERATION` before it can enter another state or
82
+ publish. If stale work independently rejects, its original source/format
83
+ error is preserved and `SetError()` is suppressed on the detached handle.
84
+
85
+ Candidate work is a normal `Wait()` root and blocks synchronous MotherLode
86
+ replacement while active. `Register({ motherLode })` rejects with
87
+ `CJS_RESMAN_ACTIVE_RESOURCE_OPERATIONS` while queued or direct mutations are
88
+ active, including a reload candidate. Normal `Wait()` drains queued roots and
89
+ candidate lineages; a direct caller must await its own load/prepare promise
90
+ before retrying replacement. Canonical and candidate authority prevent late
91
+ publication, but started source or format work is not yet aborted;
92
+ deterministic cleanup applies to the staged candidate resource itself.
93
+
94
+ ## Divergence from Carbon
95
+
96
+ This availability contract intentionally differs from Carbon.
97
+ `BlueAsyncRes::Reload` cancels/joins work, releases dependent cached data, and
98
+ reloads the same canonical object in place; failure can therefore leave that
99
+ stable handle bad. Carbon MotherLode replacement also switches immediately and
100
+ has no prepare-success gate or rollback. Runtime-resource instead preserves
101
+ the last published good handle until a distinct candidate has succeeded, and
102
+ never silently retargets existing JavaScript references.
103
+
104
+ ## Related documentation
105
+
106
+ - [MotherLode identity, cache, and retention](motherlode-cache.md)
107
+ - [Queues and the Wait fence](queues.md)
@@ -0,0 +1,113 @@
1
+ # Texture arrays and update generations
2
+
3
+ Status: Evolving
4
+ Scope: `@carbonenginejs/runtime-resource`
5
+ Audience: Users and integrators
6
+ Summary: Defines the texture-array proxy surface, the requested/prepared revision boundary, and the adapter commit contract.
7
+
8
+ ## Layer proxies
9
+
10
+ Texture-array resources expose one ordinary-looking proxy per ordered layer:
11
+
12
+ ```js
13
+ const textureArray = new CjsTextureArrayRes({
14
+ paths: [
15
+ "res:/detail1.dds",
16
+ "res:/detail2.dds",
17
+ "res:/detail3.dds"
18
+ ],
19
+ layerNames: [ "Detail1Map", "Detail2Map", "Detail3Map" ],
20
+ updateScheduler: resource => frameQueue.add(resource)
21
+ });
22
+
23
+ const detail2 = textureArray.GetLayerParameter(1);
24
+ detail2.SetValue("res:/replacement.dds");
25
+
26
+ detail2.textureRes === textureArray; // true
27
+ ```
28
+
29
+ Proxy setters only update their source path and invalidate the parent. The
30
+ parent is scheduled once even if several proxies change in the same frame.
31
+ The next-frame consumer calls `Update()` or `ConsumeUpdateRequest()` to
32
+ obtain one immutable ordered snapshot. Runtime-resource does not know which
33
+ shader metadata caused the aggregate request; shader packages and engine
34
+ adapters map public parameter names to layer indices.
35
+
36
+ Public effect parameters remain separate from these internal proxies. Their
37
+ authored paths and individual 2D source resources are not replaced by the
38
+ aggregate. An engine-owned, non-persisted bridge mirrors public changes into
39
+ the fixed internal layers.
40
+
41
+ ## Update generations
42
+
43
+ `CjsTextureArrayRes` is a derived multi-source resource with an explicit
44
+ requested/prepared revision boundary:
45
+
46
+ ```text
47
+ proxy/source change
48
+ -> requested revision + dirty layer
49
+ -> one scheduled next-frame snapshot
50
+ -> consumed/in-flight request
51
+ -> adapter candidate preparation
52
+ -> guarded adapter + prepared-revision publication
53
+ ```
54
+
55
+ `ConsumeUpdateRequest()` produces an immutable snapshot and marks that
56
+ revision in flight. A current consumed revision may be completed through
57
+ `CommitPreparedAdapterRevision()`, failed through `FailUpdateRequest()`, or
58
+ returned to the queue through `RetryUpdateRequest()`. Commit-before-consume
59
+ and stale commits are rejected; rejected candidate allocations are destroyed
60
+ by default.
61
+
62
+ Publication installs the adapter allocation and prepared revision before
63
+ completion events run. The result returns the displaced allocation to the
64
+ adapter owner for post-publication destruction. A reentrant source change may
65
+ therefore request a newer revision without allowing stale completion to
66
+ replace it. The previous prepared allocation and `IsGood()` remain usable
67
+ while a replacement is pending or if replacement preparation fails.
68
+
69
+ `Ready()` is specialized for this derived resource: it resolves when the
70
+ generation requested at call time has been published, rather than delegating
71
+ to a single-source object loader. Initial preparation failure rejects it.
72
+
73
+ ## Adapter commit example
74
+
75
+ Consumed snapshots are explicit in-flight generations. An adapter either
76
+ publishes the current candidate atomically, requeues retryable work, or
77
+ records failure:
78
+
79
+ ```js
80
+ const request = textureArray.ConsumeUpdateRequest();
81
+
82
+ try {
83
+ const candidate = await adapter.PrepareTextureArray(request);
84
+ const result = textureArray.CommitPreparedAdapterRevision(
85
+ request.revision,
86
+ "webgpu",
87
+ candidate
88
+ );
89
+
90
+ // A rejected/stale candidate is destroyed by the commit method by default.
91
+ // The adapter owns disposal of a successfully displaced allocation.
92
+ result.displaced?.destroy();
93
+ } catch (error) {
94
+ textureArray.FailUpdateRequest(request.revision, error, { retry: true });
95
+ }
96
+
97
+ await textureArray.Ready(); // the generation requested at call time
98
+ ```
99
+
100
+ ## Sources and topology
101
+
102
+ Logical paths and resolved sources are independent. `SetLayerResource()`
103
+ attaches a resolved or LOD-specific source without rewriting the logical
104
+ requested path or persistence. `TouchLayer()` invalidates an in-place source
105
+ revision. `HandleAdapterLoss()` drops an unusable adapter allocation and
106
+ schedules a complete topology rebuild. Topology-changing snapshots set
107
+ `topologyChanged: true` and report only valid current layer indices in
108
+ `dirtyLayers`.
109
+
110
+ ## Related documentation
111
+
112
+ - [Architecture and boundaries](../architecture.md)
113
+ - [Texture CPU pipeline and LOD membership](texture-pipeline.md)