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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (383) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +278 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__fixtures__/field-shape-fixture.d.ts +16 -0
  5. package/dist/__fixtures__/field-shape-fixture.d.ts.map +1 -0
  6. package/dist/__tests__/__fixtures__/inspect-scripts.snapshot.d.ts +3 -0
  7. package/dist/__tests__/__fixtures__/inspect-scripts.snapshot.d.ts.map +1 -0
  8. package/dist/__tests__/archetype.unit.test.d.ts +2 -0
  9. package/dist/__tests__/archetype.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/array-handle-element.test-d.d.ts +2 -0
  11. package/dist/__tests__/array-handle-element.test-d.d.ts.map +1 -0
  12. package/dist/__tests__/buffer-array-vocab.test-d.d.ts +2 -0
  13. package/dist/__tests__/buffer-array-vocab.test-d.d.ts.map +1 -0
  14. package/dist/__tests__/buffer-field-input-widen.unit.test.d.ts +2 -0
  15. package/dist/__tests__/buffer-field-input-widen.unit.test.d.ts.map +1 -0
  16. package/dist/__tests__/buffer-pool.test-d.d.ts +2 -0
  17. package/dist/__tests__/buffer-pool.test-d.d.ts.map +1 -0
  18. package/dist/__tests__/change-detection.unit.test.d.ts +2 -0
  19. package/dist/__tests__/change-detection.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/command-buffer.test.d.ts +2 -0
  21. package/dist/__tests__/command-buffer.test.d.ts.map +1 -0
  22. package/dist/__tests__/commands-deferred.unit.test.d.ts +2 -0
  23. package/dist/__tests__/commands-deferred.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/component-catalog.test.d.ts +2 -0
  25. package/dist/__tests__/component-catalog.test.d.ts.map +1 -0
  26. package/dist/__tests__/component-definition-identity.unit.test.d.ts +2 -0
  27. package/dist/__tests__/component-definition-identity.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/component-nan-preflight.unit.test.d.ts +2 -0
  29. package/dist/__tests__/component-nan-preflight.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/component-schema.test-d.d.ts +2 -0
  31. package/dist/__tests__/component-schema.test-d.d.ts.map +1 -0
  32. package/dist/__tests__/component-schema.test.d.ts +2 -0
  33. package/dist/__tests__/component-schema.test.d.ts.map +1 -0
  34. package/dist/__tests__/define-component-options.test-d.d.ts +2 -0
  35. package/dist/__tests__/define-component-options.test-d.d.ts.map +1 -0
  36. package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts +2 -0
  37. package/dist/__tests__/deprecated-vocab-rejection.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/despawn-all.unit.test.d.ts +2 -0
  39. package/dist/__tests__/despawn-all.unit.test.d.ts.map +1 -0
  40. package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts +2 -0
  41. package/dist/__tests__/ecs-core-reduction.characterization.test.d.ts.map +1 -0
  42. package/dist/__tests__/ecs-core-reduction.perf.test.d.ts +2 -0
  43. package/dist/__tests__/ecs-core-reduction.perf.test.d.ts.map +1 -0
  44. package/dist/__tests__/ecs-error-code-31.test-d.d.ts +2 -0
  45. package/dist/__tests__/ecs-error-code-31.test-d.d.ts.map +1 -0
  46. package/dist/__tests__/entity-disabling.unit.test.d.ts +2 -0
  47. package/dist/__tests__/entity-disabling.unit.test.d.ts.map +1 -0
  48. package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts +2 -0
  49. package/dist/__tests__/entity-handle-codec-parity.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/errors.unit.test.d.ts +2 -0
  51. package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts +2 -0
  53. package/dist/__tests__/execution-conflict-boundary.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/execution-shared-storage.unit.test.d.ts +2 -0
  55. package/dist/__tests__/execution-shared-storage.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/externalization-portable-value.unit.test.d.ts +2 -0
  57. package/dist/__tests__/externalization-portable-value.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/externalization-projection.unit.test.d.ts +2 -0
  59. package/dist/__tests__/externalization-projection.unit.test.d.ts.map +1 -0
  60. package/dist/__tests__/externalization-render-read-lease.test-d.d.ts +2 -0
  61. package/dist/__tests__/externalization-render-read-lease.test-d.d.ts.map +1 -0
  62. package/dist/__tests__/externalization-render-read-lease.unit.test.d.ts +2 -0
  63. package/dist/__tests__/externalization-render-read-lease.unit.test.d.ts.map +1 -0
  64. package/dist/__tests__/externalization-root-surface.unit.test.d.ts +2 -0
  65. package/dist/__tests__/externalization-root-surface.unit.test.d.ts.map +1 -0
  66. package/dist/__tests__/field-enum-labels-reflection.test.d.ts +2 -0
  67. package/dist/__tests__/field-enum-labels-reflection.test.d.ts.map +1 -0
  68. package/dist/__tests__/field-shape-fixture.test.d.ts +2 -0
  69. package/dist/__tests__/field-shape-fixture.test.d.ts.map +1 -0
  70. package/dist/__tests__/field-transient-reflection.test.d.ts +2 -0
  71. package/dist/__tests__/field-transient-reflection.test.d.ts.map +1 -0
  72. package/dist/__tests__/field-value-type.test-d.d.ts +2 -0
  73. package/dist/__tests__/field-value-type.test-d.d.ts.map +1 -0
  74. package/dist/__tests__/fixed-update.unit.test.d.ts +2 -0
  75. package/dist/__tests__/fixed-update.unit.test.d.ts.map +1 -0
  76. package/dist/__tests__/handle.test-d.d.ts +2 -0
  77. package/dist/__tests__/handle.test-d.d.ts.map +1 -0
  78. package/dist/__tests__/hierarchy.unit.test.d.ts +2 -0
  79. package/dist/__tests__/hierarchy.unit.test.d.ts.map +1 -0
  80. package/dist/__tests__/managed-array-vocab.test-d.d.ts +2 -0
  81. package/dist/__tests__/managed-array-vocab.test-d.d.ts.map +1 -0
  82. package/dist/__tests__/minimal-example.test-d.d.ts +2 -0
  83. package/dist/__tests__/minimal-example.test-d.d.ts.map +1 -0
  84. package/dist/__tests__/name-read-types.test-d.d.ts +2 -0
  85. package/dist/__tests__/name-read-types.test-d.d.ts.map +1 -0
  86. package/dist/__tests__/plugin-service.test.d.ts +2 -0
  87. package/dist/__tests__/plugin-service.test.d.ts.map +1 -0
  88. package/dist/__tests__/query-combinations.unit.test.d.ts +2 -0
  89. package/dist/__tests__/query-combinations.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts +2 -0
  91. package/dist/__tests__/query-light-extract-flat-column-ratio.perf.test.d.ts.map +1 -0
  92. package/dist/__tests__/query-storage-trends.perf.test.d.ts +2 -0
  93. package/dist/__tests__/query-storage-trends.perf.test.d.ts.map +1 -0
  94. package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts +2 -0
  95. package/dist/__tests__/query-trs-flat-column-ratio.perf.test.d.ts.map +1 -0
  96. package/dist/__tests__/query.unit.test.d.ts +2 -0
  97. package/dist/__tests__/query.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/relationship-index.perf.test.d.ts +2 -0
  99. package/dist/__tests__/relationship-index.perf.test.d.ts.map +1 -0
  100. package/dist/__tests__/relationship-index.test.d.ts +2 -0
  101. package/dist/__tests__/relationship-index.test.d.ts.map +1 -0
  102. package/dist/__tests__/scalar-field-type-owner.test-d.d.ts +2 -0
  103. package/dist/__tests__/scalar-field-type-owner.test-d.d.ts.map +1 -0
  104. package/dist/__tests__/schedule-determinism.unit.test.d.ts +2 -0
  105. package/dist/__tests__/schedule-determinism.unit.test.d.ts.map +1 -0
  106. package/dist/__tests__/schedule-inspection.unit.test.d.ts +2 -0
  107. package/dist/__tests__/schedule-inspection.unit.test.d.ts.map +1 -0
  108. package/dist/__tests__/schedule-scope-error.unit.test.d.ts +2 -0
  109. package/dist/__tests__/schedule-scope-error.unit.test.d.ts.map +1 -0
  110. package/dist/__tests__/schedule-token.unit.test.d.ts +2 -0
  111. package/dist/__tests__/schedule-token.unit.test.d.ts.map +1 -0
  112. package/dist/__tests__/schema-vocab.test-d.d.ts +2 -0
  113. package/dist/__tests__/schema-vocab.test-d.d.ts.map +1 -0
  114. package/dist/__tests__/serialization.unit.test.d.ts +2 -0
  115. package/dist/__tests__/serialization.unit.test.d.ts.map +1 -0
  116. package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts +2 -0
  117. package/dist/__tests__/shared-kernel-eligibility.test-d.d.ts.map +1 -0
  118. package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts +2 -0
  119. package/dist/__tests__/shared-kernel-eligibility.unit.test.d.ts.map +1 -0
  120. package/dist/__tests__/shared-ref-store.unit.test.d.ts +2 -0
  121. package/dist/__tests__/shared-ref-store.unit.test.d.ts.map +1 -0
  122. package/dist/__tests__/sparse-tag.unit.test.d.ts +2 -0
  123. package/dist/__tests__/sparse-tag.unit.test.d.ts.map +1 -0
  124. package/dist/__tests__/spawn-field-error.test-d.d.ts +2 -0
  125. package/dist/__tests__/spawn-field-error.test-d.d.ts.map +1 -0
  126. package/dist/__tests__/stale-error.test-d.d.ts +2 -0
  127. package/dist/__tests__/stale-error.test-d.d.ts.map +1 -0
  128. package/dist/__tests__/string-view-symmetry.test-d.d.ts +2 -0
  129. package/dist/__tests__/string-view-symmetry.test-d.d.ts.map +1 -0
  130. package/dist/__tests__/string-vocab.test-d.d.ts +2 -0
  131. package/dist/__tests__/string-vocab.test-d.d.ts.map +1 -0
  132. package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts +2 -0
  133. package/dist/__tests__/structure-epoch-boundaries.unit.test.d.ts.map +1 -0
  134. package/dist/__tests__/structure-epoch.unit.test.d.ts +2 -0
  135. package/dist/__tests__/structure-epoch.unit.test.d.ts.map +1 -0
  136. package/dist/__tests__/table-archetype-mapping.unit.test.d.ts +2 -0
  137. package/dist/__tests__/table-archetype-mapping.unit.test.d.ts.map +1 -0
  138. package/dist/__tests__/time.unit.test.d.ts +2 -0
  139. package/dist/__tests__/time.unit.test.d.ts.map +1 -0
  140. package/dist/__tests__/types.test-d.d.ts +2 -0
  141. package/dist/__tests__/types.test-d.d.ts.map +1 -0
  142. package/dist/__tests__/unique-ref-store.unit.test.d.ts +2 -0
  143. package/dist/__tests__/unique-ref-store.unit.test.d.ts.map +1 -0
  144. package/dist/__tests__/utils/handle-numeric.d.ts +14 -0
  145. package/dist/__tests__/utils/handle-numeric.d.ts.map +1 -0
  146. package/dist/__tests__/validation-root-surface.unit.test.d.ts +2 -0
  147. package/dist/__tests__/validation-root-surface.unit.test.d.ts.map +1 -0
  148. package/dist/__tests__/visibility-atomic-write.unit.test.d.ts +2 -0
  149. package/dist/__tests__/visibility-atomic-write.unit.test.d.ts.map +1 -0
  150. package/dist/__tests__/world-change-journal.unit.test.d.ts +2 -0
  151. package/dist/__tests__/world-change-journal.unit.test.d.ts.map +1 -0
  152. package/dist/__tests__/world-characterization.unit.test.d.ts +2 -0
  153. package/dist/__tests__/world-characterization.unit.test.d.ts.map +1 -0
  154. package/dist/__tests__/world-consumer-inference.test-d.d.ts +2 -0
  155. package/dist/__tests__/world-consumer-inference.test-d.d.ts.map +1 -0
  156. package/dist/__tests__/world-health.contract.test.d.ts +2 -0
  157. package/dist/__tests__/world-health.contract.test.d.ts.map +1 -0
  158. package/dist/__tests__/world-inspection.test.d.ts +2 -0
  159. package/dist/__tests__/world-inspection.test.d.ts.map +1 -0
  160. package/dist/__tests__/world-internal-identity.unit.test.d.ts +2 -0
  161. package/dist/__tests__/world-internal-identity.unit.test.d.ts.map +1 -0
  162. package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts +2 -0
  163. package/dist/__tests__/world-managed-roundtrip.unit.test.d.ts.map +1 -0
  164. package/dist/__tests__/world-scheduling.m4.test.d.ts +2 -0
  165. package/dist/__tests__/world-scheduling.m4.test.d.ts.map +1 -0
  166. package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts +2 -0
  167. package/dist/__tests__/world-unique-roundtrip.unit.test.d.ts.map +1 -0
  168. package/dist/__tests__/world.test-d.d.ts +2 -0
  169. package/dist/__tests__/world.test-d.d.ts.map +1 -0
  170. package/dist/buffer-pool.d.ts +109 -0
  171. package/dist/buffer-pool.d.ts.map +1 -0
  172. package/dist/commands.d.ts +93 -0
  173. package/dist/commands.d.ts.map +1 -0
  174. package/dist/component-default-fallback.d.ts +88 -0
  175. package/dist/component-default-fallback.d.ts.map +1 -0
  176. package/dist/component-schema.d.ts +34 -0
  177. package/dist/component-schema.d.ts.map +1 -0
  178. package/dist/component-value-validate.d.ts +29 -0
  179. package/dist/component-value-validate.d.ts.map +1 -0
  180. package/dist/component.d.ts +581 -0
  181. package/dist/component.d.ts.map +1 -0
  182. package/dist/entity-handle.d.ts +68 -0
  183. package/dist/entity-handle.d.ts.map +1 -0
  184. package/dist/entity.d.ts +86 -0
  185. package/dist/entity.d.ts.map +1 -0
  186. package/dist/errors/query-and-component-errors.d.ts +132 -0
  187. package/dist/errors/query-and-component-errors.d.ts.map +1 -0
  188. package/dist/errors/relationship-errors.d.ts +112 -0
  189. package/dist/errors/relationship-errors.d.ts.map +1 -0
  190. package/dist/errors/sprite-and-shared-errors.d.ts +107 -0
  191. package/dist/errors/sprite-and-shared-errors.d.ts.map +1 -0
  192. package/dist/errors/validation-errors.d.ts +229 -0
  193. package/dist/errors/validation-errors.d.ts.map +1 -0
  194. package/dist/errors.d.ts +763 -0
  195. package/dist/errors.d.ts.map +1 -0
  196. package/dist/execution/shared-kernel.d.ts +106 -0
  197. package/dist/execution/shared-kernel.d.ts.map +1 -0
  198. package/dist/externalization/index.d.ts +31 -0
  199. package/dist/externalization/index.d.ts.map +1 -0
  200. package/dist/externalization/index.mjs +298 -0
  201. package/dist/externalization/index.mjs.map +1 -0
  202. package/dist/index.d.ts +261 -0
  203. package/dist/index.d.ts.map +1 -0
  204. package/dist/index.mjs +7422 -0
  205. package/dist/index.mjs.map +1 -0
  206. package/dist/internal.mjs +479 -0
  207. package/dist/internal.mjs.map +1 -0
  208. package/dist/plugin-service.d.ts +11 -0
  209. package/dist/plugin-service.d.ts.map +1 -0
  210. package/dist/projection/index.d.ts +103 -0
  211. package/dist/projection/index.d.ts.map +1 -0
  212. package/dist/projection/index.mjs +615 -0
  213. package/dist/projection/index.mjs.map +1 -0
  214. package/dist/query/query.d.ts +58 -0
  215. package/dist/query/query.d.ts.map +1 -0
  216. package/dist/relationship-index.d.ts +81 -0
  217. package/dist/relationship-index.d.ts.map +1 -0
  218. package/dist/resource.d.ts +70 -0
  219. package/dist/resource.d.ts.map +1 -0
  220. package/dist/schedule-token.d.ts +16 -0
  221. package/dist/schedule-token.d.ts.map +1 -0
  222. package/dist/schedule.d.ts +271 -0
  223. package/dist/schedule.d.ts.map +1 -0
  224. package/dist/shared-ref-store.d.ts +144 -0
  225. package/dist/shared-ref-store.d.ts.map +1 -0
  226. package/dist/shared.d.ts +3 -0
  227. package/dist/shared.d.ts.map +1 -0
  228. package/dist/shared.mjs +364 -0
  229. package/dist/shared.mjs.map +1 -0
  230. package/dist/storage/archetype-graph.d.ts +54 -0
  231. package/dist/storage/archetype-graph.d.ts.map +1 -0
  232. package/dist/storage/archetype.d.ts +23 -0
  233. package/dist/storage/archetype.d.ts.map +1 -0
  234. package/dist/storage/change-detection.d.ts +40 -0
  235. package/dist/storage/change-detection.d.ts.map +1 -0
  236. package/dist/storage/column.d.ts +111 -0
  237. package/dist/storage/column.d.ts.map +1 -0
  238. package/dist/storage/table.d.ts +29 -0
  239. package/dist/storage/table.d.ts.map +1 -0
  240. package/dist/time.d.ts +57 -0
  241. package/dist/time.d.ts.map +1 -0
  242. package/dist/unique-ref-store.d.ts +105 -0
  243. package/dist/unique-ref-store.d.ts.map +1 -0
  244. package/dist/world-change-journal.d.ts +34 -0
  245. package/dist/world-change-journal.d.ts.map +1 -0
  246. package/dist/world-component-access.d.ts +265 -0
  247. package/dist/world-component-access.d.ts.map +1 -0
  248. package/dist/world-component-storage.d.ts +282 -0
  249. package/dist/world-component-storage.d.ts.map +1 -0
  250. package/dist/world-core.d.ts +39 -0
  251. package/dist/world-core.d.ts.map +1 -0
  252. package/dist/world-entity-lifecycle.d.ts +30 -0
  253. package/dist/world-entity-lifecycle.d.ts.map +1 -0
  254. package/dist/world-internal.d.ts +14 -0
  255. package/dist/world-internal.d.ts.map +1 -0
  256. package/dist/world-scheduling.d.ts +30 -0
  257. package/dist/world-scheduling.d.ts.map +1 -0
  258. package/dist/world.d.ts +620 -0
  259. package/dist/world.d.ts.map +1 -0
  260. package/package.json +77 -0
  261. package/src/__fixtures__/field-shape-fixture.ts +30 -0
  262. package/src/__tests__/__fixtures__/inspect-scripts.snapshot.ts +2 -0
  263. package/src/__tests__/archetype.unit.test.ts +1837 -0
  264. package/src/__tests__/array-handle-element.test-d.ts +34 -0
  265. package/src/__tests__/buffer-array-vocab.test-d.ts +53 -0
  266. package/src/__tests__/buffer-field-input-widen.unit.test.ts +99 -0
  267. package/src/__tests__/buffer-pool.test-d.ts +32 -0
  268. package/src/__tests__/change-detection.unit.test.ts +86 -0
  269. package/src/__tests__/command-buffer.test.ts +179 -0
  270. package/src/__tests__/commands-deferred.unit.test.ts +68 -0
  271. package/src/__tests__/component-catalog.test.ts +59 -0
  272. package/src/__tests__/component-definition-identity.unit.test.ts +24 -0
  273. package/src/__tests__/component-nan-preflight.unit.test.ts +186 -0
  274. package/src/__tests__/component-schema.test-d.ts +119 -0
  275. package/src/__tests__/component-schema.test.ts +55 -0
  276. package/src/__tests__/define-component-options.test-d.ts +35 -0
  277. package/src/__tests__/deprecated-vocab-rejection.unit.test.ts +59 -0
  278. package/src/__tests__/despawn-all.unit.test.ts +17 -0
  279. package/src/__tests__/ecs-core-reduction.characterization.test.ts +130 -0
  280. package/src/__tests__/ecs-core-reduction.perf.test.ts +260 -0
  281. package/src/__tests__/ecs-error-code-31.test-d.ts +42 -0
  282. package/src/__tests__/entity-disabling.unit.test.ts +37 -0
  283. package/src/__tests__/entity-handle-codec-parity.unit.test.ts +126 -0
  284. package/src/__tests__/errors.unit.test.ts +1456 -0
  285. package/src/__tests__/execution-conflict-boundary.unit.test.ts +123 -0
  286. package/src/__tests__/execution-shared-storage.unit.test.ts +40 -0
  287. package/src/__tests__/externalization-portable-value.unit.test.ts +275 -0
  288. package/src/__tests__/externalization-projection.unit.test.ts +325 -0
  289. package/src/__tests__/externalization-render-read-lease.test-d.ts +30 -0
  290. package/src/__tests__/externalization-render-read-lease.unit.test.ts +79 -0
  291. package/src/__tests__/externalization-root-surface.unit.test.ts +33 -0
  292. package/src/__tests__/field-enum-labels-reflection.test.ts +66 -0
  293. package/src/__tests__/field-shape-fixture.test.ts +61 -0
  294. package/src/__tests__/field-transient-reflection.test.ts +59 -0
  295. package/src/__tests__/field-value-type.test-d.ts +57 -0
  296. package/src/__tests__/fixed-update.unit.test.ts +78 -0
  297. package/src/__tests__/handle.test-d.ts +86 -0
  298. package/src/__tests__/hierarchy.unit.test.ts +2534 -0
  299. package/src/__tests__/managed-array-vocab.test-d.ts +137 -0
  300. package/src/__tests__/minimal-example.test-d.ts +42 -0
  301. package/src/__tests__/name-read-types.test-d.ts +72 -0
  302. package/src/__tests__/plugin-service.test.ts +13 -0
  303. package/src/__tests__/query-combinations.unit.test.ts +42 -0
  304. package/src/__tests__/query-light-extract-flat-column-ratio.perf.test.ts +252 -0
  305. package/src/__tests__/query-storage-trends.perf.test.ts +104 -0
  306. package/src/__tests__/query-trs-flat-column-ratio.perf.test.ts +194 -0
  307. package/src/__tests__/query.unit.test.ts +196 -0
  308. package/src/__tests__/relationship-index.perf.test.ts +126 -0
  309. package/src/__tests__/relationship-index.test.ts +70 -0
  310. package/src/__tests__/scalar-field-type-owner.test-d.ts +71 -0
  311. package/src/__tests__/schedule-determinism.unit.test.ts +36 -0
  312. package/src/__tests__/schedule-inspection.unit.test.ts +81 -0
  313. package/src/__tests__/schedule-scope-error.unit.test.ts +63 -0
  314. package/src/__tests__/schedule-token.unit.test.ts +54 -0
  315. package/src/__tests__/schema-vocab.test-d.ts +103 -0
  316. package/src/__tests__/serialization.unit.test.ts +2740 -0
  317. package/src/__tests__/shared-kernel-eligibility.test-d.ts +18 -0
  318. package/src/__tests__/shared-kernel-eligibility.unit.test.ts +78 -0
  319. package/src/__tests__/shared-ref-store.unit.test.ts +646 -0
  320. package/src/__tests__/sparse-tag.unit.test.ts +201 -0
  321. package/src/__tests__/spawn-field-error.test-d.ts +41 -0
  322. package/src/__tests__/stale-error.test-d.ts +208 -0
  323. package/src/__tests__/string-view-symmetry.test-d.ts +26 -0
  324. package/src/__tests__/string-vocab.test-d.ts +49 -0
  325. package/src/__tests__/structure-epoch-boundaries.unit.test.ts +39 -0
  326. package/src/__tests__/structure-epoch.unit.test.ts +48 -0
  327. package/src/__tests__/table-archetype-mapping.unit.test.ts +78 -0
  328. package/src/__tests__/time.unit.test.ts +79 -0
  329. package/src/__tests__/types.test-d.ts +38 -0
  330. package/src/__tests__/unique-ref-store.unit.test.ts +348 -0
  331. package/src/__tests__/utils/handle-numeric.ts +30 -0
  332. package/src/__tests__/validation-root-surface.unit.test.ts +15 -0
  333. package/src/__tests__/visibility-atomic-write.unit.test.ts +82 -0
  334. package/src/__tests__/world-change-journal.unit.test.ts +97 -0
  335. package/src/__tests__/world-characterization.unit.test.ts +408 -0
  336. package/src/__tests__/world-consumer-inference.test-d.ts +16 -0
  337. package/src/__tests__/world-health.contract.test.ts +87 -0
  338. package/src/__tests__/world-inspection.test.ts +37 -0
  339. package/src/__tests__/world-internal-identity.unit.test.ts +17 -0
  340. package/src/__tests__/world-managed-roundtrip.unit.test.ts +98 -0
  341. package/src/__tests__/world-scheduling.m4.test.ts +52 -0
  342. package/src/__tests__/world-unique-roundtrip.unit.test.ts +357 -0
  343. package/src/__tests__/world.test-d.ts +156 -0
  344. package/src/buffer-pool.ts +357 -0
  345. package/src/commands.ts +644 -0
  346. package/src/component-default-fallback.ts +236 -0
  347. package/src/component-schema.ts +92 -0
  348. package/src/component-value-validate.ts +135 -0
  349. package/src/component.ts +1268 -0
  350. package/src/entity-handle.ts +104 -0
  351. package/src/entity.ts +140 -0
  352. package/src/errors/query-and-component-errors.ts +200 -0
  353. package/src/errors/relationship-errors.ts +198 -0
  354. package/src/errors/sprite-and-shared-errors.ts +231 -0
  355. package/src/errors/validation-errors.ts +506 -0
  356. package/src/errors.ts +1464 -0
  357. package/src/execution/shared-kernel.ts +392 -0
  358. package/src/externalization/index.ts +170 -0
  359. package/src/index.ts +414 -0
  360. package/src/plugin-service.ts +35 -0
  361. package/src/projection/index.ts +318 -0
  362. package/src/query/query.ts +606 -0
  363. package/src/relationship-index.ts +175 -0
  364. package/src/resource.ts +106 -0
  365. package/src/schedule-token.ts +40 -0
  366. package/src/schedule.ts +846 -0
  367. package/src/shared-ref-store.ts +418 -0
  368. package/src/shared.ts +23 -0
  369. package/src/storage/archetype-graph.ts +166 -0
  370. package/src/storage/archetype.ts +78 -0
  371. package/src/storage/change-detection.ts +231 -0
  372. package/src/storage/column.ts +247 -0
  373. package/src/storage/table.ts +153 -0
  374. package/src/time.ts +122 -0
  375. package/src/unique-ref-store.ts +278 -0
  376. package/src/world-change-journal.ts +84 -0
  377. package/src/world-component-access.ts +1421 -0
  378. package/src/world-component-storage.ts +1178 -0
  379. package/src/world-core.ts +74 -0
  380. package/src/world-entity-lifecycle.ts +539 -0
  381. package/src/world-internal.ts +75 -0
  382. package/src/world-scheduling.ts +499 -0
  383. package/src/world.ts +1399 -0
@@ -0,0 +1,506 @@
1
+ import type { Component, ComponentSchema, FieldReflection } from '../component';
2
+ import { componentDefinition } from '../component-schema';
3
+
4
+ export class TimeDeltaInvalidError extends Error {
5
+ override readonly name = 'TimeDeltaInvalidError';
6
+ readonly code = 'time-delta-invalid' as const;
7
+ readonly expected = 'a finite delta greater than or equal to 0';
8
+ readonly hint = 'Call world.update(deltaSeconds) with a finite non-negative delta.';
9
+ readonly detail: { readonly received: number };
10
+
11
+ constructor(received: number) {
12
+ super(
13
+ `Invalid world.update delta: ${received}.\n expected: a finite delta greater than or equal to 0\n hint: Call world.update(deltaSeconds) with a finite non-negative delta.`,
14
+ );
15
+ this.detail = { received };
16
+ }
17
+ }
18
+
19
+ export class TimeConfigInvalidError extends Error {
20
+ override readonly name = 'TimeConfigInvalidError';
21
+ readonly code = 'time-config-invalid' as const;
22
+ readonly expected: string;
23
+ readonly hint = 'Increase maxDeltaSeconds or decrease maxStepsPerUpdate or fixedDeltaSeconds.';
24
+ readonly detail: {
25
+ readonly fixedDeltaSeconds: number;
26
+ readonly maxStepsPerUpdate: number;
27
+ readonly maxDeltaSeconds: number;
28
+ };
29
+
30
+ constructor(detail: TimeConfigInvalidError['detail']) {
31
+ const expected = 'maxDeltaSeconds >= (maxStepsPerUpdate + 1) * fixedDeltaSeconds';
32
+ super(
33
+ `Invalid World time policy.\n expected: ${expected}\n hint: Increase maxDeltaSeconds or decrease maxStepsPerUpdate or fixedDeltaSeconds.`,
34
+ );
35
+ this.expected = expected;
36
+ this.detail = detail;
37
+ }
38
+ }
39
+
40
+ export class ScheduleScopeMismatchError extends Error {
41
+ override readonly name = 'ScheduleScopeMismatchError';
42
+ readonly code = 'schedule-scope-mismatch' as const;
43
+ readonly expected: string;
44
+ readonly hint: string;
45
+ readonly detail: {
46
+ readonly sourceSchedule: string;
47
+ readonly targetSchedule: string;
48
+ readonly reference?: string;
49
+ };
50
+
51
+ constructor(sourceSchedule: string, targetSchedule: string, reference?: string) {
52
+ const expected = `a reference owned by ${sourceSchedule}`;
53
+ const hint = `The referenced item belongs to ${targetSchedule}; register and order it in ${sourceSchedule}.`;
54
+ super(`Schedule scope mismatch.\n expected: ${expected}\n hint: ${hint}`);
55
+ this.expected = expected;
56
+ this.hint = hint;
57
+ this.detail = { sourceSchedule, targetSchedule, ...(reference ? { reference } : {}) };
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Returned when a closed enum field receives a value outside its reflected
63
+ * labels. The write owner runs this before any archetype or column mutation.
64
+ */
65
+ export class ComponentFieldInvalidValueError extends Error {
66
+ override readonly name = 'ComponentFieldInvalidValueError';
67
+ readonly code = 'component-field-invalid-value' as const;
68
+ readonly hint: string;
69
+ readonly expected: string;
70
+ readonly detail: {
71
+ readonly entity: number | undefined;
72
+ readonly component: string;
73
+ readonly field: string;
74
+ readonly received: unknown;
75
+ readonly allowedValues: Readonly<Record<string, number>>;
76
+ };
77
+
78
+ constructor(
79
+ entity: number | undefined,
80
+ component: string,
81
+ field: string,
82
+ received: unknown,
83
+ allowedValues: Readonly<Record<string, number>>,
84
+ ) {
85
+ const entries = Object.entries(allowedValues)
86
+ .map(([label, value]) => `${label}=${value}`)
87
+ .join(', ');
88
+ const expected = `${component}.${field} in { ${entries} }`;
89
+ const hint = `Set ${component}.${field} to one of the reflected enum values: ${entries}`;
90
+ super(
91
+ `${component}.${field} received an invalid enum value.\n` +
92
+ ` code: component-field-invalid-value\n` +
93
+ ` component: ${component}\n` +
94
+ ` field: ${field}\n` +
95
+ ` received: ${String(received)}\n` +
96
+ ` expected: ${expected}\n` +
97
+ ` hint: ${hint}`,
98
+ );
99
+ this.hint = hint;
100
+ this.expected = expected;
101
+ this.detail = { entity, component, field, received, allowedValues };
102
+ }
103
+ }
104
+
105
+ export class ComponentNumericValueInvalidError extends Error {
106
+ override readonly name = 'ComponentNumericValueInvalidError';
107
+ readonly code = 'component-numeric-value-invalid' as const;
108
+ readonly expected = 'a numeric value other than NaN';
109
+ readonly hint: string;
110
+ readonly detail: {
111
+ readonly entity: number | undefined;
112
+ readonly component: string;
113
+ readonly field: string;
114
+ readonly received: number;
115
+ readonly index?: number;
116
+ };
117
+
118
+ constructor(
119
+ entity: number | undefined,
120
+ component: string,
121
+ field: string,
122
+ received: number,
123
+ index?: number,
124
+ ) {
125
+ const location =
126
+ index === undefined ? `${component}.${field}` : `${component}.${field}[${index}]`;
127
+ const hint = `Replace NaN at ${location} with an authored numeric value; Number.POSITIVE_INFINITY remains valid where the component domain permits it.`;
128
+ super(
129
+ `${location} received NaN.\n` +
130
+ ` code: component-numeric-value-invalid\n` +
131
+ ` expected: a numeric value other than NaN\n` +
132
+ ` hint: ${hint}`,
133
+ );
134
+ this.hint = hint;
135
+ this.detail = {
136
+ entity,
137
+ component,
138
+ field,
139
+ received,
140
+ ...(index === undefined ? {} : { index }),
141
+ };
142
+ }
143
+ }
144
+
145
+ const NUMERIC_FIELD_TYPES = new Set(['f32', 'f64', 'i32', 'u32', 'i16', 'u16', 'i8', 'u8', 'enum']);
146
+
147
+ export function validateNumericFieldValues<S extends ComponentSchema>(
148
+ component: Component<string, S>,
149
+ raw: Partial<Record<string, unknown>> | undefined,
150
+ entity?: number,
151
+ ): ComponentNumericValueInvalidError | null {
152
+ if (raw === undefined) return null;
153
+ const fields = componentDefinition(component).fields as Readonly<Record<string, FieldReflection>>;
154
+ const rawValues = raw as Record<string, unknown>;
155
+ for (const fieldName of Object.keys(rawValues)) {
156
+ const reflection = fields[fieldName];
157
+ if (reflection === undefined) continue;
158
+ const value = rawValues[fieldName];
159
+ if (NUMERIC_FIELD_TYPES.has(reflection.type)) {
160
+ if (typeof value === 'number' && Number.isNaN(value)) {
161
+ return new ComponentNumericValueInvalidError(entity, component.name, fieldName, value);
162
+ }
163
+ continue;
164
+ }
165
+ if (
166
+ reflection.arrayMeta === undefined ||
167
+ !NUMERIC_FIELD_TYPES.has(reflection.arrayMeta.elementType)
168
+ ) {
169
+ continue;
170
+ }
171
+ const length =
172
+ Array.isArray(value) || ArrayBuffer.isView(value)
173
+ ? (value as { readonly length?: number }).length
174
+ : undefined;
175
+ if (length === undefined) continue;
176
+ const values = value as ArrayLike<unknown>;
177
+ for (let index = 0; index < length; index++) {
178
+ const received = values[index];
179
+ if (typeof received === 'number' && Number.isNaN(received)) {
180
+ return new ComponentNumericValueInvalidError(
181
+ entity,
182
+ component.name,
183
+ fieldName,
184
+ received,
185
+ index,
186
+ );
187
+ }
188
+ }
189
+ }
190
+ return null;
191
+ }
192
+
193
+ /**
194
+ * Returned before an ECS write when a managed `array<T>` field receives a
195
+ * value that the storage boundary cannot interpret as an array payload. The
196
+ * old column writer treated arbitrary objects as an empty payload, which
197
+ * silently changed the row while retaining no evidence of the caller error.
198
+ */
199
+ export class ManagedArrayInvalidValueError extends Error {
200
+ override readonly name = 'ManagedArrayInvalidValueError';
201
+ readonly code = 'managed-array-invalid-value' as const;
202
+ readonly expected = 'an Array or TypedArray payload (or null/undefined to clear it)';
203
+ readonly hint: string;
204
+ readonly detail: {
205
+ readonly component: string;
206
+ readonly field: string;
207
+ readonly fieldType: string;
208
+ readonly actualValue: unknown;
209
+ };
210
+
211
+ constructor(componentName: string, fieldName: string, fieldType: string, actualValue: unknown) {
212
+ const hint =
213
+ `Set ${componentName}.${fieldName} to a plain array or TypedArray matching ` +
214
+ `${fieldType}; use null or undefined to clear the managed value.`;
215
+ super(
216
+ `${componentName}.${fieldName}: managed array received an invalid value.\n` +
217
+ ` code: managed-array-invalid-value\n` +
218
+ ` fieldType: ${fieldType}\n` +
219
+ ` expected: an Array or TypedArray payload (or null/undefined to clear it)\n` +
220
+ ` hint: ${hint}`,
221
+ );
222
+ this.hint = hint;
223
+ this.detail = { component: componentName, field: fieldName, fieldType, actualValue };
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Validate the closed enum fields present in a write payload. Enums without
229
+ * labels remain open numeric fields for compatibility with existing schemas.
230
+ */
231
+ export function validateEnumFieldValues<S extends ComponentSchema>(
232
+ component: Component<string, S>,
233
+ raw: Partial<Record<string, unknown>> | undefined,
234
+ entity?: number,
235
+ ): ComponentFieldInvalidValueError | null {
236
+ if (raw === undefined) return null;
237
+ const fields = componentDefinition(component).fields as Readonly<Record<string, FieldReflection>>;
238
+ const rawValues = raw as Record<string, unknown>;
239
+ for (const fieldName of Object.keys(rawValues)) {
240
+ const reflection = fields[fieldName];
241
+ if (reflection?.type !== 'enum' || reflection.labels === undefined) continue;
242
+ const value = rawValues[fieldName];
243
+ const allowedValues = Object.values(reflection.labels);
244
+ if (typeof value !== 'number' || !Number.isInteger(value) || !allowedValues.includes(value)) {
245
+ return new ComponentFieldInvalidValueError(
246
+ entity,
247
+ component.name,
248
+ fieldName,
249
+ value,
250
+ reflection.labels,
251
+ );
252
+ }
253
+ }
254
+ return null;
255
+ }
256
+
257
+ // ────────────────────────────────────────────────────────────────────────────
258
+ // feat-20260519-light-casters-point-spot-pbr w2 — closed-union evolution +1.
259
+ //
260
+ // Adds 1 new member 'spawn-light-invalid-bounds' to EcsErrorCode (23 -> 24).
261
+ // AGENTS.md section Error model evolution contract: minor (add member only).
262
+ // Triggered by PointLight / SpotLight spawn-time payload validation
263
+ // (plan-strategy D-S3 a). detail.field three-branch
264
+ // ('range' | 'innerOuter' | 'outerNinety') keeps the four bound-violation
265
+ // shapes under one error code so callers narrow first on `.code` then on
266
+ // `.detail.field` (charter P3 progressive disclosure).
267
+ // ────────────────────────────────────────────────────────────────────────────
268
+
269
+ /**
270
+ * Returned via `Result.err` from `world.spawn` when a PointLight or SpotLight
271
+ * payload field is out of the documented bound. Four bound violations share
272
+ * one `.code` and discriminate via `.detail.field`:
273
+ *
274
+ * - `range` — PointLight / SpotLight `range < 0` or `Number.isNaN(range)`.
275
+ * Use `Number.POSITIVE_INFINITY` for an unlimited range or a non-negative
276
+ * meter value.
277
+ * - `innerOuter` — SpotLight `outerConeDeg <= innerConeDeg`. Inner cone is
278
+ * the saturated bright region; outer cone is the falloff edge.
279
+ * - `outerNinety` — SpotLight `outerConeDeg > 90`. KHR_lights_punctual upper
280
+ * bound. A spot light cone wider than 90 degrees becomes a point light;
281
+ * use PointLight instead.
282
+ * - `direction` — DirectionalLight / SpotLight `direction` is missing or a
283
+ * zero vector `[0, 0, 0]`. Direction has no default (there is no universal
284
+ * default direction): omitting it lands the array layer-3 all-zero, which is
285
+ * the same illegal state as an explicit zero vector. Supply a non-zero
286
+ * direction (feat-20260709 M2 / D-1, add-only union member).
287
+ *
288
+ * `.code = 'spawn-light-invalid-bounds'`
289
+ * `.detail.field` is derived from `keyof typeof SPAWN_LIGHT_INVALID_BOUNDS_POLICY`;
290
+ * `.detail.got` is `number | readonly number[]`.
291
+ * `.hint` — names the offending field plus the valid replacement form.
292
+ */
293
+ const SPAWN_LIGHT_INVALID_BOUNDS_POLICY = {
294
+ range: {
295
+ expected: 'range >= 0 or Number.POSITIVE_INFINITY',
296
+ hint: (componentName: string, got: number | readonly number[]) =>
297
+ `${componentName}.range = ${got} is invalid; use Number.POSITIVE_INFINITY for unlimited range, or a non-negative meter value`,
298
+ },
299
+ innerOuter: {
300
+ expected: 'outerConeDeg > innerConeDeg',
301
+ hint: (componentName: string, got: number | readonly number[]) =>
302
+ `${componentName}.outerConeDeg <= innerConeDeg (got ${got}); inner cone is the saturated bright region, outer cone is the falloff edge; outerConeDeg > innerConeDeg required`,
303
+ },
304
+ outerNinety: {
305
+ expected: 'outerConeDeg <= 90 (KHR_lights_punctual upper bound)',
306
+ hint: (componentName: string, got: number | readonly number[]) =>
307
+ `${componentName}.outerConeDeg = ${got} > 90; a spot light cone wider than 90 degrees becomes a point light; use PointLight instead`,
308
+ },
309
+ direction: {
310
+ expected: 'direction is a non-zero [x, y, z] vector',
311
+ hint: (componentName: string, got: number | readonly number[]) =>
312
+ `${componentName}.direction is missing or a zero vector (got ${JSON.stringify(got)}); direction has no default, provide a non-zero direction, e.g. [-0.5, -1, -0.3]`,
313
+ },
314
+ } satisfies Record<
315
+ string,
316
+ {
317
+ readonly expected: string;
318
+ readonly hint: (componentName: string, got: number | readonly number[]) => string;
319
+ }
320
+ >;
321
+
322
+ export class SpawnLightInvalidBoundsError extends Error {
323
+ override readonly name = 'SpawnLightInvalidBoundsError';
324
+ readonly code = 'spawn-light-invalid-bounds' as const;
325
+ readonly hint: string;
326
+ readonly expected: string;
327
+ readonly detail: {
328
+ readonly field: keyof typeof SPAWN_LIGHT_INVALID_BOUNDS_POLICY;
329
+ readonly got: number | readonly number[];
330
+ };
331
+
332
+ constructor(
333
+ componentName: string,
334
+ field: keyof typeof SPAWN_LIGHT_INVALID_BOUNDS_POLICY,
335
+ got: number | readonly number[],
336
+ ) {
337
+ const policy = SPAWN_LIGHT_INVALID_BOUNDS_POLICY[field];
338
+ const hint = policy.hint(componentName, got);
339
+ const expectedStr = policy.expected;
340
+ super(
341
+ `${componentName}: spawn payload bound violation.\n` +
342
+ ` code: spawn-light-invalid-bounds\n` +
343
+ ` component: ${componentName}\n` +
344
+ ` field: ${field}\n` +
345
+ ` got: ${got}\n` +
346
+ ` expected: ${expectedStr}\n` +
347
+ ` hint: ${hint}`,
348
+ );
349
+ this.hint = hint;
350
+ this.expected = expectedStr;
351
+ this.detail = { field, got };
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Returned via `Result.err` from resource-setter helpers (e.g.
357
+ * `setTransparentSortConfig`) when a numeric payload field violates the
358
+ * closed bound declared by the resource contract. The first consumer is
359
+ * `TransparentSortConfig.mode ∈ {0, 1, 2}` (plan-strategy D-4); future
360
+ * resource validators with the same shape reuse this code by routing
361
+ * through `.detail.receivedKey` to disambiguate which resource validator
362
+ * surfaced the failure.
363
+ *
364
+ * Closed-set kebab code consistent with `spawn-light-invalid-bounds`
365
+ * (feat-20260519 / w2); AI users consume via `switch (err.code)` exhaustive
366
+ * narrows + `err.detail.receivedMode` (or `err.detail.receivedKey` /
367
+ * `err.expected`) property access — never string-parse the message.
368
+ *
369
+ * `.code = 'resource-invalid-value'`
370
+ * `.detail = { receivedMode: number; receivedKey?: string }`
371
+ * `.hint` — direct copy-paste recovery (e.g. "0=layer-z, 1=layer-y,
372
+ * 2=layer-yz" for the sort-config case).
373
+ * `.expected` — the bound contract literal (e.g. "mode ∈ {0, 1, 2}").
374
+ *
375
+ * @reuses RhiError structured shape — same `.code / .expected / .hint /
376
+ * .detail` quadruple AI users consume across rhi + ecs.
377
+ */
378
+ export class ResourceInvalidValueError extends Error {
379
+ override readonly name = 'ResourceInvalidValueError';
380
+ readonly code = 'resource-invalid-value' as const;
381
+ readonly hint: string;
382
+ readonly expected: string;
383
+ readonly detail: { readonly receivedMode: number; readonly receivedKey?: string };
384
+
385
+ constructor(
386
+ expected: string,
387
+ hint: string,
388
+ detail: { readonly receivedMode: number; readonly receivedKey?: string },
389
+ ) {
390
+ const keyClause = detail.receivedKey === undefined ? '' : ` key: ${detail.receivedKey}\n`;
391
+ super(
392
+ `resource: invalid value.\n` +
393
+ ` code: resource-invalid-value\n` +
394
+ keyClause +
395
+ ` receivedMode: ${detail.receivedMode}\n` +
396
+ ` expected: ${expected}\n` +
397
+ ` hint: ${hint}`,
398
+ );
399
+ this.hint = hint;
400
+ this.expected = expected;
401
+ this.detail = detail;
402
+ }
403
+ }
404
+
405
+ // ────────────────────────────────────────────────────────────────────────────
406
+ // feat-20260521-sprite-atlas-animation M1 T-05 — closed-union evolution +1.
407
+ //
408
+ // Adds 1 new member 'sprite-animation-invalid' to EcsErrorCode (25 -> 26).
409
+ // AGENTS.md §Error model evolution contract: minor (add member only).
410
+ // Same-shape add-only mirror of SpawnLightInvalidBoundsError (feat-20260519
411
+ // w2 line 736-776) and ResourceInvalidValueError (feat-20260520 w13 line
412
+ // 862) — the kebab `'<noun>-invalid-...'` series keeps `switch (err.code)`
413
+ // exhaustive narrows visually consistent (charter P4 consistent abstraction;
414
+ // research F-7 candidate A).
415
+ //
416
+ // Triggered by `spriteAnimationTickSystem` (packages/runtime/src/systems/
417
+ // sprite-animation-tick.ts, landed in M4 T-23) when an entity's
418
+ // `SpriteAnimation` row violates one of two runtime invariants:
419
+ //
420
+ // - field='regions-length' -> `regions.length !== frameCount * 4`
421
+ // - field='frame-duration' -> `frameDuration <= 0`
422
+ //
423
+ // `.detail.field` two-branch (charter P3: AI users branch once on
424
+ // `err.code` and once on `err.detail.field` to reach the recovery hint
425
+ // without parsing the message). Plan-strategy section 2 D-1 binds the
426
+ // detail field shape; M4 T-19 / T-20 / T-21 cover the runtime fail-fast
427
+ // paths end-to-end.
428
+ // ────────────────────────────────────────────────────────────────────────────
429
+
430
+ /**
431
+ * Returned via `Result.err` from `spriteAnimationTickSystem` (M4 T-23) when
432
+ * an entity's `SpriteAnimation` row violates a runtime invariant.
433
+ * Two invariants share one `.code` and discriminate via `.detail.field`:
434
+ *
435
+ * - `regions-length` — `SpriteAnimation.regions.length !== frameCount * 4`.
436
+ * `regions` packs `[uMin, vMin, uW, vH]` per frame so the length must be
437
+ * exactly `frameCount * 4`. Detail carries the offending `regionsLength`
438
+ * alongside the declared `frameCount` so the hint can spell the exact
439
+ * delta in callsite-friendly numbers.
440
+ * - `frame-duration` — `SpriteAnimation.frameDuration <= 0` (covers both
441
+ * `frameDuration === 0` and `frameDuration < 0`; T-21 binds the negative
442
+ * case to the same arm so AI users handle both via a single
443
+ * `if (err.detail.field === 'frame-duration')` branch — charter P4
444
+ * consistent abstraction).
445
+ *
446
+ * `.code = 'sprite-animation-invalid'`
447
+ * `.detail = { field: 'regions-length', regionsLength, frameCount } |
448
+ * { field: 'frame-duration', frameDuration }`
449
+ *
450
+ * Two top-level detail variants give each `.field` branch its own
451
+ * required sub-field shape so AI users get strong narrowing inside
452
+ * `switch (err.detail.field)` without optional sub-fields bleeding
453
+ * across branches (mirrors `SpawnLightInvalidBoundsError`'s shared
454
+ * `got: number` shape but adapted because regions-length /
455
+ * frame-duration carry different sub-field counts).
456
+ *
457
+ * `.hint` — names the offending invariant plus the valid replacement form.
458
+ */
459
+ export class SpriteAnimationInvalidError extends Error {
460
+ override readonly name = 'SpriteAnimationInvalidError';
461
+ readonly code = 'sprite-animation-invalid' as const;
462
+ readonly hint: string;
463
+ readonly expected: string;
464
+ readonly detail:
465
+ | {
466
+ readonly field: 'regions-length';
467
+ readonly regionsLength: number;
468
+ readonly frameCount: number;
469
+ }
470
+ | {
471
+ readonly field: 'frame-duration';
472
+ readonly frameDuration: number;
473
+ };
474
+
475
+ private static resolvePolicy(detail: SpriteAnimationInvalidError['detail']): {
476
+ readonly expected: string;
477
+ readonly hint: string;
478
+ } {
479
+ switch (detail.field) {
480
+ case 'regions-length':
481
+ return {
482
+ expected: 'SpriteAnimation.regions.length === frameCount * 4',
483
+ hint: `SpriteAnimation.regions.length = ${detail.regionsLength} does not match frameCount * 4 = ${detail.frameCount * 4}; pack 4 floats [uMin, vMin, uW, vH] per frame (see <name>.atlas.meta.json sidecar 'regions' map)`,
484
+ };
485
+ case 'frame-duration':
486
+ return {
487
+ expected: 'SpriteAnimation.frameDuration > 0',
488
+ hint: `SpriteAnimation.frameDuration = ${detail.frameDuration} is invalid; use a positive seconds-per-frame value (e.g. 0.1 = 10 fps)`,
489
+ };
490
+ }
491
+ }
492
+
493
+ constructor(detail: SpriteAnimationInvalidError['detail']) {
494
+ const policy = SpriteAnimationInvalidError.resolvePolicy(detail);
495
+ super(
496
+ `SpriteAnimation: invariant violated.\n` +
497
+ ` code: sprite-animation-invalid\n` +
498
+ ` field: ${detail.field}\n` +
499
+ ` expected: ${policy.expected}\n` +
500
+ ` hint: ${policy.hint}`,
501
+ );
502
+ this.hint = policy.hint;
503
+ this.expected = policy.expected;
504
+ this.detail = detail;
505
+ }
506
+ }