@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,552 @@
1
+ import { assert } from "../assert.js";
2
+ import { IndexedFloatMinHeap } from "../collection/heap/IndexedFloatMinHeap.js";
3
+ import { lowbias32 } from "../math/hash/lowbias32.js";
4
+ import { WFCCellChoice } from "./WFCCellChoice.js";
5
+ import { WFCLattice } from "./WFCLattice.js";
6
+ import { WFCRuleTable, wfc_tail_mask } from "./WFCRuleTable.js";
7
+ import { WFCTileWeights } from "./WFCTileWeights.js";
8
+
9
+ /**
10
+ * Entropy differences below this are not meaningful — the heap stores a `float32`, whose precision
11
+ * near `ln(2)` is about `6e-8` — so a jitter of this size orders ties without reordering anything
12
+ * real.
13
+ * @type {number}
14
+ */
15
+ const ENTROPY_JITTER_SCALE = 1e-6;
16
+
17
+ /**
18
+ * Possibility counts are integers a whole unit apart, so half a unit of jitter separates ties at
19
+ * any count a `float32` can still resolve.
20
+ * @type {number}
21
+ */
22
+ const COUNT_JITTER_SCALE = 0.5;
23
+
24
+ /**
25
+ * Initial capacity of the undo trail, in entries. Grows by doubling; never shrinks.
26
+ * @type {number}
27
+ */
28
+ const TRAIL_INITIAL_CAPACITY = 1024;
29
+
30
+ /**
31
+ * `u32` words per trail entry: the address that was written, and the bits that were cleared.
32
+ * @type {number}
33
+ */
34
+ const TRAIL_ENTRY_WORDS = 2;
35
+
36
+ /**
37
+ * `u32` words per decision-stack entry: the cell, the trail length before the collapse, and the
38
+ * tile that was tried.
39
+ * @type {number}
40
+ */
41
+ const DECISION_ENTRY_WORDS = 3;
42
+
43
+ /**
44
+ * Everything a solve mutates: the wave, the aggregates derived from it, the propagation queue, the
45
+ * undo trail and the decision stack.
46
+ *
47
+ * All of it lives in the object, which is what makes {@link wfc_solve} resumable — a call that
48
+ * runs out of budget mid-propagation leaves its work here and the next call continues from it.
49
+ *
50
+ * Build one per solve. The {@link WFCRuleTable}, {@link WFCLattice} and {@link WFCTileWeights} it
51
+ * is given are read-only to it and can be shared across many.
52
+ *
53
+ * @author Alex Goldring
54
+ * @copyright Company Named Limited (c) 2026
55
+ */
56
+ export class WFCState {
57
+ /**
58
+ * @param {WFCRuleTable} rules must be finalized
59
+ * @param {WFCLattice} lattice must have the same direction count as `rules`
60
+ * @param {WFCTileWeights} weights must have the same tile count as `rules`
61
+ * @param {function():number} random RNG returning values in `[0, 1)`. Consumed once here and
62
+ * once per collapse, so a seeded generator reproduces a solve exactly.
63
+ * @param {WFCCellChoice} [cell_choice] defaults to {@link WFCCellChoice.SCANLINE}
64
+ */
65
+ constructor(rules, lattice, weights, random, cell_choice = WFCCellChoice.SCANLINE) {
66
+ assert.isInstanceOf(rules, WFCRuleTable, 'rules');
67
+ assert.isInstanceOf(lattice, WFCLattice, 'lattice');
68
+ assert.isInstanceOf(weights, WFCTileWeights, 'weights');
69
+ assert.isFunction(random, 'random');
70
+ assert.isOneOf(
71
+ cell_choice,
72
+ [WFCCellChoice.ENTROPY, WFCCellChoice.MINIMUM_REMAINING, WFCCellChoice.SCANLINE],
73
+ 'cell_choice'
74
+ );
75
+
76
+ if (!rules.finalized) {
77
+ throw new Error('rule table is not finalized; call finalize() after the last mutation');
78
+ }
79
+
80
+ if (rules.direction_count !== lattice.direction_count) {
81
+ throw new Error(`rule table has ${rules.direction_count} directions, lattice has ${lattice.direction_count}`);
82
+ }
83
+
84
+ if (rules.tile_count !== weights.tile_count) {
85
+ throw new Error(`rule table has ${rules.tile_count} tiles, weights have ${weights.tile_count}`);
86
+ }
87
+
88
+ const cell_count = lattice.cell_count;
89
+ const words_per_cell = rules.words_per_cell;
90
+
91
+ // the trail stores `cell * words_per_cell + word` in a u32 and decodes it back with an
92
+ // int32 conversion, so the bound is 2^31 rather than 2^32. A real throw: consumers strip
93
+ // assert.* out of shipped builds, and silent index wraparound is exactly what must not
94
+ // depend on a build flag.
95
+ if (cell_count * words_per_cell >= 0x80000000) {
96
+ throw new Error(`cell_count(=${cell_count}) x words_per_cell(=${words_per_cell}) must stay below 2^31`);
97
+ }
98
+
99
+ /** @type {WFCRuleTable} */
100
+ this.rules = rules;
101
+ /** @type {WFCLattice} */
102
+ this.lattice = lattice;
103
+ /** @type {WFCTileWeights} */
104
+ this.weights = weights;
105
+ /** @type {function():number} */
106
+ this.random = random;
107
+ /** @type {WFCCellChoice} */
108
+ this.cell_choice = cell_choice;
109
+
110
+ /** @type {number} */
111
+ this.cell_count = cell_count;
112
+ /** @type {number} */
113
+ this.tile_count = rules.tile_count;
114
+ /** @type {number} */
115
+ this.words_per_cell = words_per_cell;
116
+ /** @type {number} */
117
+ this.direction_count = rules.direction_count;
118
+
119
+ /**
120
+ * A domain with every tile in it, tail-masked.
121
+ * @type {Uint32Array}
122
+ */
123
+ this.full_mask = new Uint32Array(words_per_cell);
124
+ this.full_mask.fill(0xFFFFFFFF);
125
+ this.full_mask[words_per_cell - 1] = wfc_tail_mask(this.tile_count);
126
+
127
+ /**
128
+ * Seeds the per-cell tie-break that separates cells of equal score. Drawn from `random`,
129
+ * so a seeded generator reproduces the whole solve.
130
+ * @type {number}
131
+ * @private
132
+ */
133
+ this.__noise_seed = (random() * 0x100000000) >>> 0;
134
+
135
+ /**
136
+ * @type {number}
137
+ * @private
138
+ */
139
+ this.__jitter_scale = cell_choice === WFCCellChoice.ENTROPY
140
+ ? ENTROPY_JITTER_SCALE
141
+ : COUNT_JITTER_SCALE;
142
+
143
+ const f64_bytes = cell_count * 8;
144
+ const wave_bytes = cell_count * words_per_cell * 4;
145
+ const u32_bytes = cell_count * 4;
146
+
147
+ let offset = 0;
148
+
149
+ /**
150
+ * Single backing buffer for every per-cell array.
151
+ * @type {ArrayBuffer}
152
+ * @private
153
+ */
154
+ this.__data_buffer = new ArrayBuffer((f64_bytes * 2) + wave_bytes + (u32_bytes * 3) + (cell_count * 3));
155
+
156
+ /**
157
+ * Sum of the weights still possible in each cell.
158
+ *
159
+ * Maintained incrementally and therefore subject to drift; only the entropy heuristic
160
+ * reads it, and {@link wfc_state_collapse} recomputes the sum exactly rather than trusting
161
+ * it.
162
+ * @type {Float64Array}
163
+ */
164
+ this.weight_sum = new Float64Array(this.__data_buffer, offset, cell_count);
165
+ offset += f64_bytes;
166
+
167
+ /**
168
+ * Sum of `w·ln(w)` over the weights still possible in each cell.
169
+ * @type {Float64Array}
170
+ */
171
+ this.weight_log_sum = new Float64Array(this.__data_buffer, offset, cell_count);
172
+ offset += f64_bytes;
173
+
174
+ /**
175
+ * `words_per_cell` bits per cell: is tile `t` still possible here?
176
+ * @type {Uint32Array}
177
+ */
178
+ this.wave = new Uint32Array(this.__data_buffer, offset, cell_count * words_per_cell);
179
+ offset += wave_bytes;
180
+
181
+ /**
182
+ * Population count of each cell's domain, cached rather than recomputed.
183
+ * @type {Uint32Array}
184
+ */
185
+ this.possibility_count = new Uint32Array(this.__data_buffer, offset, cell_count);
186
+ offset += u32_bytes;
187
+
188
+ /**
189
+ * Cells whose domain shrank and whose neighbours have not been relaxed against them yet.
190
+ * Bounded at `cell_count` because {@link in_queue} de-duplicates.
191
+ * @type {Uint32Array}
192
+ */
193
+ this.queue = new Uint32Array(this.__data_buffer, offset, cell_count);
194
+ offset += u32_bytes;
195
+
196
+ /**
197
+ * Cells whose selection score changed during the current wave. Flushed to the heap once
198
+ * per wave rather than once per banned tile.
199
+ * @type {Uint32Array}
200
+ */
201
+ this.dirty_list = new Uint32Array(this.__data_buffer, offset, cell_count);
202
+ offset += u32_bytes;
203
+
204
+ /** @type {Uint8Array} */
205
+ this.in_queue = new Uint8Array(this.__data_buffer, offset, cell_count);
206
+ offset += cell_count;
207
+
208
+ /** @type {Uint8Array} */
209
+ this.dirty = new Uint8Array(this.__data_buffer, offset, cell_count);
210
+ offset += cell_count;
211
+
212
+ /**
213
+ * May the solver choose to collapse this cell? Cells outside the eligible set still
214
+ * constrain their neighbours through propagation; they are simply never selected.
215
+ * @type {Uint8Array}
216
+ */
217
+ this.eligible = new Uint8Array(this.__data_buffer, offset, cell_count);
218
+ offset += cell_count;
219
+
220
+ /** @type {number} */
221
+ this.queue_count = 0;
222
+ /** @type {number} */
223
+ this.dirty_count = 0;
224
+
225
+ /**
226
+ * `(address, removed_bits)` pairs, in the order they were applied.
227
+ * @type {Uint32Array}
228
+ * @private
229
+ */
230
+ this.__trail = new Uint32Array(TRAIL_INITIAL_CAPACITY * TRAIL_ENTRY_WORDS);
231
+
232
+ /**
233
+ * Entries, not words.
234
+ * @type {number}
235
+ * @private
236
+ */
237
+ this.__trail_length = 0;
238
+
239
+ /**
240
+ * `(cell, trail_mark, tried_tile)` triples.
241
+ * @type {Uint32Array}
242
+ * @private
243
+ */
244
+ this.__decisions = new Uint32Array(64 * DECISION_ENTRY_WORDS);
245
+
246
+ /** @type {number} @private */
247
+ this.__decision_count = 0;
248
+
249
+ /**
250
+ * Scratch for the propagator: the target bits not yet known to be supported.
251
+ * Owned by the state rather than the module, so two solves can interleave.
252
+ * @type {Uint32Array}
253
+ * @private
254
+ */
255
+ this.__uncovered = new Uint32Array(words_per_cell);
256
+
257
+ /**
258
+ * Set by {@link wfc_state_ban_bits} when a cell's domain empties. Checked once per
259
+ * relaxation rather than returned through every call.
260
+ * @type {boolean}
261
+ */
262
+ this.contradiction = false;
263
+
264
+ /**
265
+ * Eligible cells that still have more than one possibility. The solve is finished when it
266
+ * reaches zero.
267
+ * @type {number}
268
+ */
269
+ this.undecided_eligible = 0;
270
+
271
+ /** @type {number} */
272
+ this.backtrack_count = 0;
273
+
274
+ /**
275
+ * High-water mark of {@link __trail_length}, in entries — what a pathological rule set
276
+ * costs in memory.
277
+ * @type {number}
278
+ */
279
+ this.trail_peak = 0;
280
+
281
+ /**
282
+ * Where {@link WFCCellChoice.SCANLINE} has reached.
283
+ * @type {number}
284
+ * @private
285
+ */
286
+ this.__scan_cursor = 0;
287
+
288
+ /**
289
+ * Present only for the policies that need a priority queue.
290
+ * @type {IndexedFloatMinHeap|null}
291
+ */
292
+ this.heap = cell_choice === WFCCellChoice.SCANLINE
293
+ ? null
294
+ : new IndexedFloatMinHeap(cell_count);
295
+
296
+ // every cell starts with every tile, and every cell starts queued: the initial sweep is
297
+ // the propagation queue being pre-filled. Each relaxation in it has a pristine source, so
298
+ // the rule table's full_support answers it in O(words_per_cell).
299
+ for (let cell = 0; cell < cell_count; cell++) {
300
+ const base = cell * words_per_cell;
301
+
302
+ for (let w = 0; w < words_per_cell; w++) {
303
+ this.wave[base + w] = this.full_mask[w];
304
+ }
305
+
306
+ this.possibility_count[cell] = this.tile_count;
307
+ this.weight_sum[cell] = weights.total_weight;
308
+ this.weight_log_sum[cell] = weights.total_weight_log_weight;
309
+
310
+ this.queue[cell] = cell;
311
+ this.in_queue[cell] = 1;
312
+ this.eligible[cell] = 1;
313
+ }
314
+
315
+ this.queue_count = cell_count;
316
+
317
+ this.reseed_selection();
318
+ }
319
+
320
+ /**
321
+ * Bytes held by the per-cell arrays. The heap, trail and decision stack are separate and grow.
322
+ * @returns {number}
323
+ */
324
+ get byte_size() {
325
+ return this.__data_buffer.byteLength;
326
+ }
327
+
328
+ /**
329
+ * Trail entries currently recorded.
330
+ * @returns {number}
331
+ */
332
+ get trail_length() {
333
+ return this.__trail_length;
334
+ }
335
+
336
+ /**
337
+ * Decisions currently on the stack — the depth of the search.
338
+ * @returns {number}
339
+ */
340
+ get decision_count() {
341
+ return this.__decision_count;
342
+ }
343
+
344
+ /**
345
+ * Selection score of a cell under the current {@link WFCCellChoice}; lower is chosen sooner.
346
+ *
347
+ * @param {number} cell
348
+ * @returns {number}
349
+ */
350
+ score_of(cell) {
351
+ const jitter = ((lowbias32(cell ^ this.__noise_seed) >>> 8) / 0x1000000) * this.__jitter_scale;
352
+
353
+ if (this.cell_choice === WFCCellChoice.ENTROPY) {
354
+ const sum = this.weight_sum[cell];
355
+
356
+ return Math.log(sum) - (this.weight_log_sum[cell] / sum) + jitter;
357
+ }
358
+
359
+ return this.possibility_count[cell] + jitter;
360
+ }
361
+
362
+ /**
363
+ * Make everything decided so far permanent: drop the decision stack and the undo trail, so no
364
+ * later contradiction can rewind past this point.
365
+ *
366
+ * The wave is untouched — this is about what the *search* may reconsider, not what the grid
367
+ * holds. Afterwards a contradiction reports {@link WFCStatus.UNSATISFIABLE} rather than
368
+ * dismantling work already treated as finished.
369
+ */
370
+ commit() {
371
+ this.__decision_count = 0;
372
+ this.__trail_length = 0;
373
+ }
374
+
375
+ /**
376
+ * Rebuild the selection queue and the undecided count from the current wave.
377
+ *
378
+ * Called at construction, and by {@link wfc_state_set_eligible} when the eligible set changes.
379
+ *
380
+ * @param {Uint32Array|number[]} [cells] the eligible set when it is already known, which makes
381
+ * the rebuild proportional to it rather than to the whole grid. Must not repeat a cell;
382
+ * {@link wfc_state_set_eligible} asserts that.
383
+ */
384
+ reseed_selection(cells) {
385
+ const { cell_count, heap, possibility_count, eligible } = this;
386
+
387
+ this.__scan_cursor = 0;
388
+
389
+ // clear only the flags that are actually set — `dirty[cell]` is raised only when the cell
390
+ // is appended to `dirty_list`, so the list names every one. A `fill(0)` here is a
391
+ // whole-grid memset, which block solving pays once per block.
392
+ for (let i = 0; i < this.dirty_count; i++) {
393
+ this.dirty[this.dirty_list[i]] = 0;
394
+ }
395
+
396
+ this.dirty_count = 0;
397
+
398
+ if (heap !== null) {
399
+ heap.clear();
400
+ }
401
+
402
+ let undecided = 0;
403
+
404
+ const count = cells === undefined ? cell_count : cells.length;
405
+
406
+ for (let i = 0; i < count; i++) {
407
+ const cell = cells === undefined ? i : cells[i];
408
+
409
+ if (eligible[cell] === 0 || possibility_count[cell] <= 1) {
410
+ continue;
411
+ }
412
+
413
+ undecided++;
414
+
415
+ if (heap !== null) {
416
+ heap.insert(cell, this.score_of(cell));
417
+ }
418
+ }
419
+
420
+ this.undecided_eligible = undecided;
421
+ }
422
+
423
+ /**
424
+ * Record that `bits` were cleared at `address`, so backtracking can put them back.
425
+ *
426
+ * @param {number} address `cell * words_per_cell + word`
427
+ * @param {number} bits the bits that were actually cleared
428
+ */
429
+ trail_push(address, bits) {
430
+ const length = this.__trail_length;
431
+
432
+ if ((length + 1) * TRAIL_ENTRY_WORDS > this.__trail.length) {
433
+ const grown = new Uint32Array(this.__trail.length * 2);
434
+
435
+ grown.set(this.__trail);
436
+
437
+ this.__trail = grown;
438
+ }
439
+
440
+ const word = length * TRAIL_ENTRY_WORDS;
441
+
442
+ this.__trail[word] = address;
443
+ this.__trail[word + 1] = bits;
444
+
445
+ const next = length + 1;
446
+
447
+ this.__trail_length = next;
448
+
449
+ if (next > this.trail_peak) {
450
+ this.trail_peak = next;
451
+ }
452
+ }
453
+
454
+ /**
455
+ * @param {number} entry
456
+ * @returns {number} the address written by that trail entry
457
+ */
458
+ trail_address(entry) {
459
+ return this.__trail[entry * TRAIL_ENTRY_WORDS];
460
+ }
461
+
462
+ /**
463
+ * @param {number} entry
464
+ * @returns {number} the bits that entry cleared
465
+ */
466
+ trail_bits(entry) {
467
+ return this.__trail[entry * TRAIL_ENTRY_WORDS + 1];
468
+ }
469
+
470
+ /**
471
+ * Drop trail entries above `length`. The caller has already restored the wave from them.
472
+ * @param {number} length
473
+ */
474
+ trail_truncate(length) {
475
+ this.__trail_length = length;
476
+ }
477
+
478
+ /**
479
+ * Push a decision the search can return to.
480
+ *
481
+ * `trail_mark` is taken before the collapse and `tile` is only known after it, so the caller
482
+ * passes both rather than this reading the current trail length.
483
+ *
484
+ * @param {number} cell
485
+ * @param {number} trail_mark trail length immediately before the collapse
486
+ * @param {number} tile
487
+ */
488
+ decision_push(cell, trail_mark, tile) {
489
+ const count = this.__decision_count;
490
+
491
+ if ((count + 1) * DECISION_ENTRY_WORDS > this.__decisions.length) {
492
+ const grown = new Uint32Array(this.__decisions.length * 2);
493
+
494
+ grown.set(this.__decisions);
495
+
496
+ this.__decisions = grown;
497
+ }
498
+
499
+ const word = count * DECISION_ENTRY_WORDS;
500
+
501
+ this.__decisions[word] = cell;
502
+ this.__decisions[word + 1] = trail_mark;
503
+ this.__decisions[word + 2] = tile;
504
+
505
+ this.__decision_count = count + 1;
506
+ }
507
+
508
+ /**
509
+ * Remove the newest decision and report it.
510
+ *
511
+ * @param {Uint32Array|number[]} out receives `[cell, trail_mark, tried_tile]`
512
+ * @returns {boolean} false when the stack was already empty
513
+ */
514
+ decision_pop(out) {
515
+ if (this.__decision_count === 0) {
516
+ return false;
517
+ }
518
+
519
+ const count = this.__decision_count - 1;
520
+ const word = count * DECISION_ENTRY_WORDS;
521
+
522
+ out[0] = this.__decisions[word];
523
+ out[1] = this.__decisions[word + 1];
524
+ out[2] = this.__decisions[word + 2];
525
+
526
+ this.__decision_count = count;
527
+
528
+ return true;
529
+ }
530
+
531
+ /**
532
+ * Scratch for {@link wfc_state_propagate}.
533
+ * @returns {Uint32Array}
534
+ */
535
+ get uncovered() {
536
+ return this.__uncovered;
537
+ }
538
+
539
+ /**
540
+ * @returns {number}
541
+ */
542
+ get scan_cursor() {
543
+ return this.__scan_cursor;
544
+ }
545
+
546
+ /**
547
+ * @param {number} v
548
+ */
549
+ set scan_cursor(v) {
550
+ this.__scan_cursor = v;
551
+ }
552
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Outcome of a call to {@link wfc_solve }.
3
+ */
4
+ export type WFCStatus = number;
5
+ export namespace WFCStatus {
6
+ let SOLVED: number;
7
+ let RUNNING: number;
8
+ let UNSATISFIABLE: number;
9
+ let EXHAUSTED: number;
10
+ }
11
+ //# sourceMappingURL=WFCStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WFCStatus.d.ts","sourceRoot":"","sources":["../../../../src/core/wfc/WFCStatus.js"],"names":[],"mappings":";;;wBAIU,MAAM"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Outcome of a call to {@link wfc_solve}.
3
+ *
4
+ * @readonly
5
+ * @enum {number}
6
+ */
7
+ export const WFCStatus = {
8
+ /**
9
+ * Every eligible cell has exactly one possibility left. The result is ready to read.
10
+ */
11
+ SOLVED: 0,
12
+ /**
13
+ * The operation budget ran out. Nothing is wrong; call again to continue. All progress,
14
+ * including a half-drained propagation queue, lives in the state.
15
+ */
16
+ RUNNING: 1,
17
+ /**
18
+ * The search space is exhausted — the instance has no solution under the caller's initial
19
+ * restrictions. This is an answer, not a failure.
20
+ */
21
+ UNSATISFIABLE: 2,
22
+ /**
23
+ * The backtrack budget ran out before the search either succeeded or proved the instance
24
+ * unsatisfiable. The conventional response is to restart with a different seed; a caller
25
+ * that wants to keep searching raises `max_backtracks` instead.
26
+ */
27
+ EXHAUSTED: 3
28
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Relative placement likelihoods, one per tile.
3
+ *
4
+ * Nothing is normalised — `[1, 9]` and `[0.1, 0.9]` behave identically.
5
+ *
6
+ * Weights must be finite and strictly positive; a non-positive one throws. A zero-weight tile
7
+ * would sit in a cell's domain, count toward its possibility count and never be selected, which
8
+ * surfaces much later as a solve that cannot finish. A tile you do not want is one you leave out
9
+ * of the rule table.
10
+ *
11
+ * Held apart from {@link WFCRuleTable} so the same structure can be solved with different
12
+ * preferences.
13
+ *
14
+ * @author Alex Goldring
15
+ * @copyright Company Named Limited (c) 2026
16
+ */
17
+ export class WFCTileWeights {
18
+ /**
19
+ * Every tile equally likely — the same as passing an array of ones.
20
+ *
21
+ * @param {number} tile_count
22
+ * @returns {WFCTileWeights}
23
+ */
24
+ static uniform(tile_count: number): WFCTileWeights;
25
+ /**
26
+ * @param {number[]|Float64Array} weights one strictly positive, finite weight per tile
27
+ */
28
+ constructor(weights: number[] | Float64Array);
29
+ /** @type {number} */
30
+ tile_count: number;
31
+ /** @type {Float64Array} */
32
+ weight: Float64Array;
33
+ /**
34
+ * `w · ln(w)`, the numerator term of the Shannon entropy of a weighted domain.
35
+ * @type {Float64Array}
36
+ */
37
+ weight_log_weight: Float64Array;
38
+ /**
39
+ * Sum over every tile — the value a pristine cell's running sum starts at.
40
+ * @type {number}
41
+ */
42
+ total_weight: number;
43
+ /**
44
+ * @type {number}
45
+ */
46
+ total_weight_log_weight: number;
47
+ }
48
+ //# sourceMappingURL=WFCTileWeights.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WFCTileWeights.d.ts","sourceRoot":"","sources":["../../../../src/core/wfc/WFCTileWeights.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH;IAwDI;;;;;OAKG;IACH,2BAHW,MAAM,GACJ,cAAc,CAM1B;IAjED;;OAEG;IACH,qBAFW,MAAM,EAAE,GAAC,YAAY,EAoD/B;IA3CG,qBAAqB;IACrB,YADW,MAAM,CACW;IAE5B,2BAA2B;IAC3B,QADW,YAAY,CACmB;IAE1C;;;OAGG;IACH,mBAFU,YAAY,CAE+B;IAuBrD;;;OAGG;IACH,cAFU,MAAM,CAES;IAEzB;;OAEG;IACH,yBAFU,MAAM,CAEwB;CAc/C"}