@woosh/meep-engine 3.29.0 → 3.30.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 (226) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/build/bundle-worker-terrain.js +1 -1
  3. package/package.json +1 -1
  4. package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
  5. package/src/core/binary/BinaryBuffer.js +14 -2
  6. package/src/core/bvh2/bvh3/BVH.d.ts.map +1 -1
  7. package/src/core/bvh2/bvh3/BVH.js +14 -1
  8. package/src/core/cache/FrequencySketch.d.ts +3 -1
  9. package/src/core/cache/FrequencySketch.d.ts.map +1 -1
  10. package/src/core/cache/FrequencySketch.js +10 -4
  11. package/src/core/cache/wtinylfu/CacheWTinylfu.d.ts.map +1 -1
  12. package/src/core/cache/wtinylfu/CacheWTinylfu.js +24 -2
  13. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts +20 -0
  14. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts.map +1 -1
  15. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +69 -4
  16. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts +48 -18
  17. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -1
  18. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +262 -70
  19. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts +25 -0
  20. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts.map +1 -0
  21. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.js +228 -0
  22. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts +13 -0
  23. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts.map +1 -1
  24. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.js +10 -128
  25. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.d.ts.map +1 -1
  26. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.js +11 -1
  27. package/src/core/graph/mn_graph_collapse_weighted_edge.js +6 -6
  28. package/src/core/math/lookup/ParameterLookupTable.d.ts.map +1 -1
  29. package/src/core/math/lookup/ParameterLookupTable.js +10 -5
  30. package/src/core/math/noise/sdnoise.d.ts.map +1 -1
  31. package/src/core/math/noise/sdnoise.js +16 -10
  32. package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
  33. package/src/core/model/object/ImmutableObjectPool.js +17 -7
  34. package/src/core/process/executor/ConcurrentExecutor.d.ts +1 -5
  35. package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
  36. package/src/core/process/executor/ConcurrentExecutor.js +95 -1
  37. package/src/core/process/task/task_find_dependency_cycle.d.ts +17 -0
  38. package/src/core/process/task/task_find_dependency_cycle.d.ts.map +1 -0
  39. package/src/core/process/task/task_find_dependency_cycle.js +81 -0
  40. package/src/core/wfc/WFCCellChoice.d.ts +17 -0
  41. package/src/core/wfc/WFCCellChoice.d.ts.map +1 -0
  42. package/src/core/wfc/WFCCellChoice.js +36 -0
  43. package/src/core/wfc/WFCLattice.d.ts +105 -0
  44. package/src/core/wfc/WFCLattice.d.ts.map +1 -0
  45. package/src/core/wfc/WFCLattice.js +235 -0
  46. package/src/core/wfc/WFCRuleTable.d.ts +172 -0
  47. package/src/core/wfc/WFCRuleTable.d.ts.map +1 -0
  48. package/src/core/wfc/WFCRuleTable.js +334 -0
  49. package/src/core/wfc/WFCState.d.ts +273 -0
  50. package/src/core/wfc/WFCState.d.ts.map +1 -0
  51. package/src/core/wfc/WFCState.js +552 -0
  52. package/src/core/wfc/WFCStatus.d.ts +11 -0
  53. package/src/core/wfc/WFCStatus.d.ts.map +1 -0
  54. package/src/core/wfc/WFCStatus.js +28 -0
  55. package/src/core/wfc/WFCTileWeights.d.ts +48 -0
  56. package/src/core/wfc/WFCTileWeights.d.ts.map +1 -0
  57. package/src/core/wfc/WFCTileWeights.js +86 -0
  58. package/src/core/wfc/WFC_UNRESOLVED.d.ts +12 -0
  59. package/src/core/wfc/WFC_UNRESOLVED.d.ts.map +1 -0
  60. package/src/core/wfc/WFC_UNRESOLVED.js +11 -0
  61. package/src/core/wfc/wfc_estimate_bytes.d.ts +24 -0
  62. package/src/core/wfc/wfc_estimate_bytes.d.ts.map +1 -0
  63. package/src/core/wfc/wfc_estimate_bytes.js +47 -0
  64. package/src/core/wfc/wfc_rule_table_tile_components.d.ts +27 -0
  65. package/src/core/wfc/wfc_rule_table_tile_components.d.ts.map +1 -0
  66. package/src/core/wfc/wfc_rule_table_tile_components.js +80 -0
  67. package/src/core/wfc/wfc_solve.d.ts +21 -0
  68. package/src/core/wfc/wfc_solve.d.ts.map +1 -0
  69. package/src/core/wfc/wfc_solve.js +154 -0
  70. package/src/core/wfc/wfc_state_apply_boundary.d.ts +18 -0
  71. package/src/core/wfc/wfc_state_apply_boundary.d.ts.map +1 -0
  72. package/src/core/wfc/wfc_state_apply_boundary.js +61 -0
  73. package/src/core/wfc/wfc_state_assign.d.ts +11 -0
  74. package/src/core/wfc/wfc_state_assign.d.ts.map +1 -0
  75. package/src/core/wfc/wfc_state_assign.js +36 -0
  76. package/src/core/wfc/wfc_state_ban_bits.d.ts +17 -0
  77. package/src/core/wfc/wfc_state_ban_bits.d.ts.map +1 -0
  78. package/src/core/wfc/wfc_state_ban_bits.js +81 -0
  79. package/src/core/wfc/wfc_state_collapse.d.ts +12 -0
  80. package/src/core/wfc/wfc_state_collapse.d.ts.map +1 -0
  81. package/src/core/wfc/wfc_state_collapse.js +73 -0
  82. package/src/core/wfc/wfc_state_flush_dirty.d.ts +12 -0
  83. package/src/core/wfc/wfc_state_flush_dirty.d.ts.map +1 -0
  84. package/src/core/wfc/wfc_state_flush_dirty.js +39 -0
  85. package/src/core/wfc/wfc_state_propagate.d.ts +14 -0
  86. package/src/core/wfc/wfc_state_propagate.d.ts.map +1 -0
  87. package/src/core/wfc/wfc_state_propagate.js +155 -0
  88. package/src/core/wfc/wfc_state_read.d.ts +11 -0
  89. package/src/core/wfc/wfc_state_read.d.ts.map +1 -0
  90. package/src/core/wfc/wfc_state_read.js +38 -0
  91. package/src/core/wfc/wfc_state_restrict.d.ts +18 -0
  92. package/src/core/wfc/wfc_state_restrict.d.ts.map +1 -0
  93. package/src/core/wfc/wfc_state_restrict.js +39 -0
  94. package/src/core/wfc/wfc_state_set_eligible.d.ts +24 -0
  95. package/src/core/wfc/wfc_state_set_eligible.d.ts.map +1 -0
  96. package/src/core/wfc/wfc_state_set_eligible.js +49 -0
  97. package/src/core/wfc/wfc_state_undo_to.d.ts +13 -0
  98. package/src/core/wfc/wfc_state_undo_to.d.ts.map +1 -0
  99. package/src/core/wfc/wfc_state_undo_to.js +84 -0
  100. package/src/core/wfc/wfc_words_per_cell.d.ts +11 -0
  101. package/src/core/wfc/wfc_words_per_cell.d.ts.map +1 -0
  102. package/src/core/wfc/wfc_words_per_cell.js +16 -0
  103. package/src/engine/ecs/EntityComponentDataset.d.ts +8 -0
  104. package/src/engine/ecs/EntityComponentDataset.d.ts.map +1 -1
  105. package/src/engine/ecs/EntityComponentDataset.js +37 -14
  106. package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts +8 -1
  107. package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts.map +1 -1
  108. package/src/engine/ecs/fow/FogOfWarRevealerSystem.js +37 -2
  109. package/src/engine/ecs/guid/UUID.d.ts.map +1 -1
  110. package/src/engine/ecs/guid/UUID.js +45 -17
  111. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.d.ts.map +1 -1
  112. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +3 -1
  113. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  114. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +366 -360
  115. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.d.ts.map +1 -1
  116. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.js +24 -10
  117. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
  118. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.js +24 -5
  119. package/src/engine/interpolation/Interpoland.d.ts +7 -1
  120. package/src/engine/interpolation/Interpoland.d.ts.map +1 -1
  121. package/src/engine/interpolation/Interpoland.js +8 -1
  122. package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -1
  123. package/src/engine/interpolation/InterpolationSystem.js +3 -0
  124. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +5 -3
  125. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -1
  126. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +14 -3
  127. package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -1
  128. package/src/engine/interpolation/pose_interpoland.js +2 -0
  129. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  130. package/src/engine/network/orchestrator/NetworkPeer.js +15 -10
  131. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
  132. package/src/engine/physics/cloth/ecs/ClothWorld.js +17 -0
  133. package/src/engine/physics/mls-mpm/MLS_MPM.d.ts.map +1 -1
  134. package/src/engine/physics/mls-mpm/MLS_MPM.js +14 -0
  135. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts +2 -1
  136. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts.map +1 -1
  137. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +1 -1
  138. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts +1 -0
  139. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -1
  140. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts +1 -0
  141. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -1
  142. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts +2 -1
  143. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -1
  144. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +12 -3
  145. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts +1 -0
  146. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -1
  147. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +1 -0
  148. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
  149. package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
  150. package/src/engine/sound/sopra/runtime/EventInstance.js +4 -1
  151. package/src/format/image/avif/av1/decode/decode_coefficients.d.ts.map +1 -1
  152. package/src/format/image/avif/av1/decode/decode_coefficients.js +248 -259
  153. package/src/format/image/avif/av1/decode/read_golomb.d.ts +21 -0
  154. package/src/format/image/avif/av1/decode/read_golomb.d.ts.map +1 -0
  155. package/src/format/image/avif/av1/decode/read_golomb.js +49 -0
  156. package/src/format/image/avif/av1/filter/loop_restoration.js +461 -451
  157. package/src/format/image/avif/av1/util/round2.d.ts +19 -2
  158. package/src/format/image/avif/av1/util/round2.d.ts.map +1 -1
  159. package/src/format/image/avif/av1/util/round2.js +51 -27
  160. package/src/format/image/avif/heif/parse_avif_file.js +24 -0
  161. package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
  162. package/src/shade/renderer/buffer/table/GPUTypedTable.js +14 -0
  163. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +46 -3
  164. package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +91 -1
  165. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +0 -13
  166. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
  167. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +0 -15
  168. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.js +5 -5
  169. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts +11 -1
  170. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts.map +1 -1
  171. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.js +11 -1
  172. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts +18 -9
  173. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts.map +1 -1
  174. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.js +20 -26
  175. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts +1 -3
  176. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts.map +1 -1
  177. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.js +2 -5
  178. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +1 -1
  179. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -1
  180. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +14 -1
  181. package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.d.ts.map +1 -1
  182. package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.js +12 -3
  183. package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.d.ts.map +1 -1
  184. package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.js +7 -1
  185. package/src/shade/renderer/lightmap/GPULightMap.d.ts.map +1 -1
  186. package/src/shade/renderer/lightmap/GPULightMap.js +349 -336
  187. package/src/shade/renderer/loader/gltf/load_gltf.d.ts.map +1 -1
  188. package/src/shade/renderer/loader/gltf/load_gltf.js +1137 -1128
  189. package/src/shade/renderer/particles/DESIGN.md +14 -5
  190. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +25 -10
  191. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  192. package/src/shade/renderer/particles/GPUParticleSystem.js +182 -38
  193. package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
  194. package/src/shade/renderer/particles/graph/groups/simplex_reference.js +16 -10
  195. package/src/shade/renderer/particles/graph_particles.d.ts +4 -2
  196. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  197. package/src/shade/renderer/particles/graph_particles.js +8 -2
  198. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  199. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +8 -2
  200. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
  201. package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +10 -4
  202. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts +31 -0
  203. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts.map +1 -0
  204. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.js +62 -0
  205. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +13 -0
  206. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  207. package/src/shade/renderer/particles/vm/ParticleVMReference.js +95 -17
  208. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts +21 -0
  209. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts.map +1 -0
  210. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js +28 -0
  211. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
  212. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +19 -2
  213. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
  214. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +19 -2
  215. package/src/shade/renderer/shader/type/WebGPUStruct.d.ts.map +1 -1
  216. package/src/shade/renderer/shader/type/WebGPUStruct.js +12 -0
  217. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts +8 -2
  218. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts.map +1 -1
  219. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.js +41 -6
  220. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +2383 -2356
  221. package/src/view/tooltip/DomTooltipManager.d.ts +7 -0
  222. package/src/view/tooltip/DomTooltipManager.d.ts.map +1 -1
  223. package/src/view/tooltip/DomTooltipManager.js +22 -0
  224. package/src/view/tooltip/DomTooltipObserver.d.ts.map +1 -1
  225. package/src/view/tooltip/DomTooltipObserver.js +13 -3
  226. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_CREASE_ANGLE.js +0 -45
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Bits that name real tiles in the final word of a `words_per_cell`-word mask.
3
+ *
4
+ * A named function because the natural spelling `(1 << (tile_count & 31)) - 1` yields `0` — every
5
+ * tile dead — at exactly 32, 64, 96 … tiles, JavaScript taking the shift count modulo 32.
6
+ *
7
+ * @param {number} tile_count
8
+ * @returns {number}
9
+ */
10
+ export function wfc_tail_mask(tile_count: number): number;
11
+ /**
12
+ * Bytes a {@link WFCRuleTable} of this shape would hold, without building one.
13
+ *
14
+ * @param {number} tile_count
15
+ * @param {number} direction_count
16
+ * @returns {number}
17
+ */
18
+ export function wfc_rule_table_byte_size(tile_count: number, direction_count: number): number;
19
+ /**
20
+ * Is the table symmetric under `d ^ 1` — does `t` allow `u` across `d` exactly when `u` allows `t`
21
+ * across `d ^ 1`?
22
+ *
23
+ * Propagation assumes this. A table built only from {@link WFCRuleTable#allow_symmetric} and
24
+ * {@link WFCRuleTable#deny_symmetric} has it by construction; check one that did not.
25
+ *
26
+ * `O(tile_count² × direction_count)` — a build-time check, not a per-solve one.
27
+ *
28
+ * @param {WFCRuleTable} rules
29
+ * @returns {boolean}
30
+ */
31
+ export function wfc_rule_table_is_symmetric(rules: WFCRuleTable): boolean;
32
+ /**
33
+ * Which adjacencies are legal, as one flat bit table.
34
+ *
35
+ * `data[((d * tile_count) + t) * words_per_cell + w]` — bit `b` set means "tile `w*32 + b` may sit
36
+ * at the neighbour reached by stepping in direction `d` away from a tile `t`".
37
+ *
38
+ * **Directions.** `direction_count` must be even. Direction `d` is `axis = d >> 1`,
39
+ * `side = d & 1`, and `d ^ 1` is the opposite direction. Propagation is sound only if the table is
40
+ * symmetric under that involution, so build one with {@link allow_symmetric} and
41
+ * {@link deny_symmetric}; {@link allow_directed} is for deliberately asymmetric tables and is the
42
+ * documented way to get it wrong. {@link wfc_rule_table_is_symmetric} checks.
43
+ *
44
+ * **Size is quadratic in the tile count** — `direction_count × tile_count × ceil(tile_count/32) × 4`
45
+ * bytes: 2 KiB at 64 tiles in 2-D, 32 KiB at 256 (about one L1), 12 MiB at 4096 in 3-D. The design
46
+ * assumes this table is small enough to stay hot, so check {@link wfc_rule_table_byte_size} before
47
+ * committing to a large tile count.
48
+ *
49
+ * Call {@link finalize} after the last mutation and before building a {@link WFCState}.
50
+ *
51
+ * @author Alex Goldring
52
+ * @copyright Company Named Limited (c) 2026
53
+ */
54
+ export class WFCRuleTable {
55
+ /**
56
+ * @param {number} tile_count number of distinct tiles, at least 1
57
+ * @param {number} direction_count `2 × dimension_count`, at least 2 and even
58
+ */
59
+ constructor(tile_count: number, direction_count: number);
60
+ /** @type {number} */
61
+ tile_count: number;
62
+ /** @type {number} */
63
+ direction_count: number;
64
+ /** @type {number} */
65
+ words_per_cell: number;
66
+ /**
67
+ * Bits at or above `tile_count` in the final word of a row. Every write masks with this,
68
+ * so no rule row can name a tile that does not exist and the wave's tail invariant is
69
+ * never threatened from this side.
70
+ * @type {number}
71
+ * @private
72
+ */
73
+ private __tail_mask;
74
+ /**
75
+ * `direction_count × tile_count` rows of `words_per_cell` words.
76
+ * @type {Uint32Array}
77
+ */
78
+ data: Uint32Array;
79
+ /**
80
+ * `full_support[d * words_per_cell + w]` — the union of every tile's row in direction `d`.
81
+ *
82
+ * Read by the propagator, which uses it to relax across a cell that has lost nothing in
83
+ * `O(words_per_cell)` rather than scanning that cell's whole domain.
84
+ *
85
+ * Built by {@link finalize}; stale until then.
86
+ * @type {Uint32Array}
87
+ */
88
+ full_support: Uint32Array;
89
+ /**
90
+ * @type {boolean}
91
+ * @private
92
+ */
93
+ private __finalized;
94
+ /**
95
+ * @returns {boolean}
96
+ */
97
+ get finalized(): boolean;
98
+ /**
99
+ * Bytes held by {@link data} and {@link full_support}.
100
+ * @returns {number}
101
+ */
102
+ get byte_size(): number;
103
+ /**
104
+ * Every adjacency legal. The starting point for a table you build by denying.
105
+ */
106
+ fill_allow(): void;
107
+ /**
108
+ * No adjacency legal. The starting point for a table you build by allowing.
109
+ */
110
+ fill_deny(): void;
111
+ /**
112
+ * Allow `tile_b` to sit on the `direction` side of `tile_a`, **and** `tile_a` to sit on the
113
+ * opposite side of `tile_b`.
114
+ *
115
+ * This is the mutator to use. The one-sided {@link allow_directed} exists for callers building
116
+ * a deliberately asymmetric table, and is the documented way to make propagation unsound.
117
+ *
118
+ * @param {number} direction
119
+ * @param {number} tile_a
120
+ * @param {number} tile_b
121
+ */
122
+ allow_symmetric(direction: number, tile_a: number, tile_b: number): void;
123
+ /**
124
+ * @param {number} direction
125
+ * @param {number} tile_a
126
+ * @param {number} tile_b
127
+ */
128
+ deny_symmetric(direction: number, tile_a: number, tile_b: number): void;
129
+ /**
130
+ * Allow `tile_b` to sit on the `direction` side of `tile_a`, and say nothing about the reverse.
131
+ * @param {number} direction
132
+ * @param {number} tile_a
133
+ * @param {number} tile_b
134
+ */
135
+ allow_directed(direction: number, tile_a: number, tile_b: number): void;
136
+ /**
137
+ * @param {number} direction
138
+ * @param {number} tile_a
139
+ * @param {number} tile_b
140
+ */
141
+ deny_directed(direction: number, tile_a: number, tile_b: number): void;
142
+ /**
143
+ * @param {number} direction
144
+ * @param {number} tile_a
145
+ * @param {number} tile_b
146
+ * @returns {boolean} may `tile_b` sit on the `direction` side of `tile_a`?
147
+ */
148
+ allows(direction: number, tile_a: number, tile_b: number): boolean;
149
+ /**
150
+ * Build {@link full_support}. Must be called after the last mutation and before a
151
+ * {@link WFCState} is built against this table; every mutator clears the flag.
152
+ *
153
+ * Builds nothing else. In particular a tile whose rule row is empty in some direction is left
154
+ * alone rather than removed, because such a tile can still sit legally on the matching border,
155
+ * where there is no neighbour that way. {@link rows_without_support} reports them.
156
+ */
157
+ finalize(): void;
158
+ /**
159
+ * Diagnostic: `(direction, tile)` pairs whose rule row is empty.
160
+ *
161
+ * Such a tile can only be placed where the lattice has no neighbour in that direction — on the
162
+ * matching border, or nowhere at all if the axis wraps. Usually it means a rule was forgotten,
163
+ * but a border-only tile is legitimate, so this is reported rather than treated as an error.
164
+ *
165
+ * @returns {{direction:number, tile:number}[]}
166
+ */
167
+ rows_without_support(): {
168
+ direction: number;
169
+ tile: number;
170
+ }[];
171
+ }
172
+ //# sourceMappingURL=WFCRuleTable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WFCRuleTable.d.ts","sourceRoot":"","sources":["../../../../src/core/wfc/WFCRuleTable.js"],"names":[],"mappings":"AAqRA;;;;;;;;GAQG;AACH,0CAHW,MAAM,GACJ,MAAM,CAMlB;AAED;;;;;;GAMG;AACH,qDAJW,MAAM,mBACN,MAAM,GACJ,MAAM,CAMlB;AAED;;;;;;;;;;;GAWG;AACH,mDAHW,YAAY,GACV,OAAO,CAkBnB;AAzUD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IACI;;;OAGG;IACH,wBAHW,MAAM,mBACN,MAAM,EAuDhB;IAtCG,qBAAqB;IACrB,YADW,MAAM,CACW;IAC5B,qBAAqB;IACrB,iBADW,MAAM,CACqB;IACtC,qBAAqB;IACrB,gBADW,MAAM,CACmB;IAEpC;;;;;;OAMG;IACH,oBAA4C;IAE5C;;;OAGG;IACH,MAFU,WAAW,CAEqD;IAE1E;;;;;;;;OAQG;IACH,cAFU,WAAW,CAEgD;IAErE;;;OAGG;IACH,oBAAwB;IAG5B;;OAEG;IACH,yBAEC;IAED;;;OAGG;IACH,wBAEC;IAED;;OAEG;IACH,mBAaC;IAED;;OAEG;IACH,kBAGC;IAED;;;;;;;;;;OAUG;IACH,2BAJW,MAAM,UACN,MAAM,UACN,MAAM,QAKhB;IAED;;;;OAIG;IACH,0BAJW,MAAM,UACN,MAAM,UACN,MAAM,QAKhB;IAED;;;;;OAKG;IACH,0BAJW,MAAM,UACN,MAAM,UACN,MAAM,QAehB;IAED;;;;OAIG;IACH,yBAJW,MAAM,UACN,MAAM,UACN,MAAM,QAehB;IAED;;;;;OAKG;IACH,kBALW,MAAM,UACN,MAAM,UACN,MAAM,GACJ,OAAO,CAanB;IAED;;;;;;;OAOG;IACH,iBAmBC;IAED;;;;;;;;OAQG;IACH,wBAFa;QAAC,SAAS,EAAC,MAAM,CAAC;QAAC,IAAI,EAAC,MAAM,CAAA;KAAC,EAAE,CAyB7C;CACJ"}
@@ -0,0 +1,334 @@
1
+ import { assert } from "../assert.js";
2
+ import { WFC_UNRESOLVED } from "./WFC_UNRESOLVED.js";
3
+ import { wfc_words_per_cell } from "./wfc_words_per_cell.js";
4
+
5
+ /**
6
+ * Which adjacencies are legal, as one flat bit table.
7
+ *
8
+ * `data[((d * tile_count) + t) * words_per_cell + w]` — bit `b` set means "tile `w*32 + b` may sit
9
+ * at the neighbour reached by stepping in direction `d` away from a tile `t`".
10
+ *
11
+ * **Directions.** `direction_count` must be even. Direction `d` is `axis = d >> 1`,
12
+ * `side = d & 1`, and `d ^ 1` is the opposite direction. Propagation is sound only if the table is
13
+ * symmetric under that involution, so build one with {@link allow_symmetric} and
14
+ * {@link deny_symmetric}; {@link allow_directed} is for deliberately asymmetric tables and is the
15
+ * documented way to get it wrong. {@link wfc_rule_table_is_symmetric} checks.
16
+ *
17
+ * **Size is quadratic in the tile count** — `direction_count × tile_count × ceil(tile_count/32) × 4`
18
+ * bytes: 2 KiB at 64 tiles in 2-D, 32 KiB at 256 (about one L1), 12 MiB at 4096 in 3-D. The design
19
+ * assumes this table is small enough to stay hot, so check {@link wfc_rule_table_byte_size} before
20
+ * committing to a large tile count.
21
+ *
22
+ * Call {@link finalize} after the last mutation and before building a {@link WFCState}.
23
+ *
24
+ * @author Alex Goldring
25
+ * @copyright Company Named Limited (c) 2026
26
+ */
27
+ export class WFCRuleTable {
28
+ /**
29
+ * @param {number} tile_count number of distinct tiles, at least 1
30
+ * @param {number} direction_count `2 × dimension_count`, at least 2 and even
31
+ */
32
+ constructor(tile_count, direction_count) {
33
+ assert.isPositiveInteger(tile_count, 'tile_count');
34
+ assert.isPositiveInteger(direction_count, 'direction_count');
35
+
36
+ if (tile_count >= WFC_UNRESOLVED) {
37
+ // the read-out sentinel would collide with a real tile id
38
+ throw new Error(`tile_count(=${tile_count}) must be below ${WFC_UNRESOLVED}`);
39
+ }
40
+
41
+ if ((direction_count & 1) !== 0) {
42
+ throw new Error(`direction_count(=${direction_count}) must be even; d ^ 1 is the opposite direction`);
43
+ }
44
+
45
+ const words_per_cell = wfc_words_per_cell(tile_count);
46
+
47
+ /** @type {number} */
48
+ this.tile_count = tile_count;
49
+ /** @type {number} */
50
+ this.direction_count = direction_count;
51
+ /** @type {number} */
52
+ this.words_per_cell = words_per_cell;
53
+
54
+ /**
55
+ * Bits at or above `tile_count` in the final word of a row. Every write masks with this,
56
+ * so no rule row can name a tile that does not exist and the wave's tail invariant is
57
+ * never threatened from this side.
58
+ * @type {number}
59
+ * @private
60
+ */
61
+ this.__tail_mask = wfc_tail_mask(tile_count);
62
+
63
+ /**
64
+ * `direction_count × tile_count` rows of `words_per_cell` words.
65
+ * @type {Uint32Array}
66
+ */
67
+ this.data = new Uint32Array(direction_count * tile_count * words_per_cell);
68
+
69
+ /**
70
+ * `full_support[d * words_per_cell + w]` — the union of every tile's row in direction `d`.
71
+ *
72
+ * Read by the propagator, which uses it to relax across a cell that has lost nothing in
73
+ * `O(words_per_cell)` rather than scanning that cell's whole domain.
74
+ *
75
+ * Built by {@link finalize}; stale until then.
76
+ * @type {Uint32Array}
77
+ */
78
+ this.full_support = new Uint32Array(direction_count * words_per_cell);
79
+
80
+ /**
81
+ * @type {boolean}
82
+ * @private
83
+ */
84
+ this.__finalized = false;
85
+ }
86
+
87
+ /**
88
+ * @returns {boolean}
89
+ */
90
+ get finalized() {
91
+ return this.__finalized;
92
+ }
93
+
94
+ /**
95
+ * Bytes held by {@link data} and {@link full_support}.
96
+ * @returns {number}
97
+ */
98
+ get byte_size() {
99
+ return this.data.byteLength + this.full_support.byteLength;
100
+ }
101
+
102
+ /**
103
+ * Every adjacency legal. The starting point for a table you build by denying.
104
+ */
105
+ fill_allow() {
106
+ const { data, words_per_cell } = this;
107
+ const last = words_per_cell - 1;
108
+ const tail = this.__tail_mask;
109
+
110
+ data.fill(0xFFFFFFFF);
111
+
112
+ // the final word of every row carries only the bits that name real tiles
113
+ for (let address = last; address < data.length; address += words_per_cell) {
114
+ data[address] = tail;
115
+ }
116
+
117
+ this.__finalized = false;
118
+ }
119
+
120
+ /**
121
+ * No adjacency legal. The starting point for a table you build by allowing.
122
+ */
123
+ fill_deny() {
124
+ this.data.fill(0);
125
+ this.__finalized = false;
126
+ }
127
+
128
+ /**
129
+ * Allow `tile_b` to sit on the `direction` side of `tile_a`, **and** `tile_a` to sit on the
130
+ * opposite side of `tile_b`.
131
+ *
132
+ * This is the mutator to use. The one-sided {@link allow_directed} exists for callers building
133
+ * a deliberately asymmetric table, and is the documented way to make propagation unsound.
134
+ *
135
+ * @param {number} direction
136
+ * @param {number} tile_a
137
+ * @param {number} tile_b
138
+ */
139
+ allow_symmetric(direction, tile_a, tile_b) {
140
+ this.allow_directed(direction, tile_a, tile_b);
141
+ this.allow_directed(direction ^ 1, tile_b, tile_a);
142
+ }
143
+
144
+ /**
145
+ * @param {number} direction
146
+ * @param {number} tile_a
147
+ * @param {number} tile_b
148
+ */
149
+ deny_symmetric(direction, tile_a, tile_b) {
150
+ this.deny_directed(direction, tile_a, tile_b);
151
+ this.deny_directed(direction ^ 1, tile_b, tile_a);
152
+ }
153
+
154
+ /**
155
+ * Allow `tile_b` to sit on the `direction` side of `tile_a`, and say nothing about the reverse.
156
+ * @param {number} direction
157
+ * @param {number} tile_a
158
+ * @param {number} tile_b
159
+ */
160
+ allow_directed(direction, tile_a, tile_b) {
161
+ assert.isNonNegativeInteger(direction, 'direction');
162
+ assert.isNonNegativeInteger(tile_a, 'tile_a');
163
+ assert.isNonNegativeInteger(tile_b, 'tile_b');
164
+ assert.lessThan(direction, this.direction_count, 'direction < direction_count');
165
+ assert.lessThan(tile_a, this.tile_count, 'tile_a < tile_count');
166
+ assert.lessThan(tile_b, this.tile_count, 'tile_b < tile_count');
167
+
168
+ const row = ((direction * this.tile_count) + tile_a) * this.words_per_cell;
169
+
170
+ this.data[row + (tile_b >> 5)] |= (1 << (tile_b & 31));
171
+
172
+ this.__finalized = false;
173
+ }
174
+
175
+ /**
176
+ * @param {number} direction
177
+ * @param {number} tile_a
178
+ * @param {number} tile_b
179
+ */
180
+ deny_directed(direction, tile_a, tile_b) {
181
+ assert.isNonNegativeInteger(direction, 'direction');
182
+ assert.isNonNegativeInteger(tile_a, 'tile_a');
183
+ assert.isNonNegativeInteger(tile_b, 'tile_b');
184
+ assert.lessThan(direction, this.direction_count, 'direction < direction_count');
185
+ assert.lessThan(tile_a, this.tile_count, 'tile_a < tile_count');
186
+ assert.lessThan(tile_b, this.tile_count, 'tile_b < tile_count');
187
+
188
+ const row = ((direction * this.tile_count) + tile_a) * this.words_per_cell;
189
+
190
+ this.data[row + (tile_b >> 5)] &= ~(1 << (tile_b & 31));
191
+
192
+ this.__finalized = false;
193
+ }
194
+
195
+ /**
196
+ * @param {number} direction
197
+ * @param {number} tile_a
198
+ * @param {number} tile_b
199
+ * @returns {boolean} may `tile_b` sit on the `direction` side of `tile_a`?
200
+ */
201
+ allows(direction, tile_a, tile_b) {
202
+ assert.isNonNegativeInteger(direction, 'direction');
203
+ assert.isNonNegativeInteger(tile_a, 'tile_a');
204
+ assert.isNonNegativeInteger(tile_b, 'tile_b');
205
+ assert.lessThan(direction, this.direction_count, 'direction < direction_count');
206
+ assert.lessThan(tile_a, this.tile_count, 'tile_a < tile_count');
207
+ assert.lessThan(tile_b, this.tile_count, 'tile_b < tile_count');
208
+
209
+ const row = ((direction * this.tile_count) + tile_a) * this.words_per_cell;
210
+
211
+ return (this.data[row + (tile_b >> 5)] & (1 << (tile_b & 31))) !== 0;
212
+ }
213
+
214
+ /**
215
+ * Build {@link full_support}. Must be called after the last mutation and before a
216
+ * {@link WFCState} is built against this table; every mutator clears the flag.
217
+ *
218
+ * Builds nothing else. In particular a tile whose rule row is empty in some direction is left
219
+ * alone rather than removed, because such a tile can still sit legally on the matching border,
220
+ * where there is no neighbour that way. {@link rows_without_support} reports them.
221
+ */
222
+ finalize() {
223
+ const { data, full_support, tile_count, direction_count, words_per_cell } = this;
224
+
225
+ full_support.fill(0);
226
+
227
+ for (let d = 0; d < direction_count; d++) {
228
+ const support_base = d * words_per_cell;
229
+ const direction_base = d * tile_count * words_per_cell;
230
+
231
+ for (let t = 0; t < tile_count; t++) {
232
+ const row = direction_base + t * words_per_cell;
233
+
234
+ for (let w = 0; w < words_per_cell; w++) {
235
+ full_support[support_base + w] |= data[row + w];
236
+ }
237
+ }
238
+ }
239
+
240
+ this.__finalized = true;
241
+ }
242
+
243
+ /**
244
+ * Diagnostic: `(direction, tile)` pairs whose rule row is empty.
245
+ *
246
+ * Such a tile can only be placed where the lattice has no neighbour in that direction — on the
247
+ * matching border, or nowhere at all if the axis wraps. Usually it means a rule was forgotten,
248
+ * but a border-only tile is legitimate, so this is reported rather than treated as an error.
249
+ *
250
+ * @returns {{direction:number, tile:number}[]}
251
+ */
252
+ rows_without_support() {
253
+ const { data, tile_count, direction_count, words_per_cell } = this;
254
+ const result = [];
255
+
256
+ for (let d = 0; d < direction_count; d++) {
257
+ const direction_base = d * tile_count * words_per_cell;
258
+
259
+ for (let t = 0; t < tile_count; t++) {
260
+ const row = direction_base + t * words_per_cell;
261
+
262
+ let any = 0;
263
+
264
+ for (let w = 0; w < words_per_cell; w++) {
265
+ any |= data[row + w];
266
+ }
267
+
268
+ if (any === 0) {
269
+ result.push({ direction: d, tile: t });
270
+ }
271
+ }
272
+ }
273
+
274
+ return result;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Bits that name real tiles in the final word of a `words_per_cell`-word mask.
280
+ *
281
+ * A named function because the natural spelling `(1 << (tile_count & 31)) - 1` yields `0` — every
282
+ * tile dead — at exactly 32, 64, 96 … tiles, JavaScript taking the shift count modulo 32.
283
+ *
284
+ * @param {number} tile_count
285
+ * @returns {number}
286
+ */
287
+ export function wfc_tail_mask(tile_count) {
288
+ const remainder = tile_count & 31;
289
+
290
+ return remainder === 0 ? 0xFFFFFFFF : (((1 << remainder) >>> 0) - 1);
291
+ }
292
+
293
+ /**
294
+ * Bytes a {@link WFCRuleTable} of this shape would hold, without building one.
295
+ *
296
+ * @param {number} tile_count
297
+ * @param {number} direction_count
298
+ * @returns {number}
299
+ */
300
+ export function wfc_rule_table_byte_size(tile_count, direction_count) {
301
+ const words_per_cell = wfc_words_per_cell(tile_count);
302
+
303
+ return (direction_count * tile_count * words_per_cell * 4) + (direction_count * words_per_cell * 4);
304
+ }
305
+
306
+ /**
307
+ * Is the table symmetric under `d ^ 1` — does `t` allow `u` across `d` exactly when `u` allows `t`
308
+ * across `d ^ 1`?
309
+ *
310
+ * Propagation assumes this. A table built only from {@link WFCRuleTable#allow_symmetric} and
311
+ * {@link WFCRuleTable#deny_symmetric} has it by construction; check one that did not.
312
+ *
313
+ * `O(tile_count² × direction_count)` — a build-time check, not a per-solve one.
314
+ *
315
+ * @param {WFCRuleTable} rules
316
+ * @returns {boolean}
317
+ */
318
+ export function wfc_rule_table_is_symmetric(rules) {
319
+ const { tile_count, direction_count } = rules;
320
+
321
+ for (let d = 0; d < direction_count; d++) {
322
+ const opposite = d ^ 1;
323
+
324
+ for (let a = 0; a < tile_count; a++) {
325
+ for (let b = 0; b < tile_count; b++) {
326
+ if (rules.allows(d, a, b) !== rules.allows(opposite, b, a)) {
327
+ return false;
328
+ }
329
+ }
330
+ }
331
+ }
332
+
333
+ return true;
334
+ }