lecodes-cli 0.19.0 → 0.20.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 (270) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +1069 -396
  3. package/package.json +14 -13
  4. package/runtime/scene-harness.json +1 -1
  5. package/runtime/sdk-types.json +1 -1
  6. package/runtime/web/assets/Roboto-Variable-DHm9jpd-.woff2 +0 -0
  7. package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  8. package/runtime/web/assets/basis-C64VHDVD.js +1 -0
  9. package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
  10. package/runtime/web/assets/createViewerLite-BvHobVhp.js +852 -0
  11. package/runtime/web/assets/draco-BiISTFcR.js +118 -0
  12. package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
  13. package/runtime/web/assets/index-CfNCCBjN.js +2 -0
  14. package/runtime/web/assets/mapViewImpl-B2JcES8l.js +810 -0
  15. package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +8 -0
  16. package/runtime/web/assets/neutral_ibl128-D-lzdkLN.ktx +0 -0
  17. package/runtime/web/assets/worker-Caf-yYEI.js +2 -0
  18. package/runtime/web/embed.html +32 -0
  19. package/runtime/web/embed.js +171 -0
  20. package/src/api.ts +302 -0
  21. package/src/browserAuth.ts +87 -0
  22. package/src/cmgenTool.ts +104 -0
  23. package/src/commands/app.ts +896 -0
  24. package/src/commands/appAndroid.ts +603 -0
  25. package/src/commands/appDesktop.ts +339 -0
  26. package/src/commands/appDesktopMac.ts +470 -0
  27. package/src/commands/appIcon.ts +187 -0
  28. package/src/commands/appShared.ts +542 -0
  29. package/src/commands/appTemplates.ts +907 -0
  30. package/src/commands/appTemplatesAndroid.ts +599 -0
  31. package/src/commands/appTemplatesGradlew.ts +9 -0
  32. package/src/commands/assets.ts +28 -0
  33. package/src/commands/clone.ts +60 -0
  34. package/src/commands/compile.ts +253 -0
  35. package/src/commands/create.ts +59 -0
  36. package/src/commands/design.ts +863 -0
  37. package/src/commands/designTemplates.ts +11 -0
  38. package/src/commands/desktop.ts +127 -0
  39. package/src/commands/dev.ts +236 -0
  40. package/src/commands/diff.ts +59 -0
  41. package/src/commands/init.ts +191 -0
  42. package/src/commands/install.ts +198 -0
  43. package/src/commands/lightmap.ts +290 -0
  44. package/src/commands/link.ts +147 -0
  45. package/src/commands/login.ts +24 -0
  46. package/src/commands/navmesh.ts +225 -0
  47. package/src/commands/pn.ts +305 -0
  48. package/src/commands/projectTemplates.ts +130 -0
  49. package/src/commands/pull.ts +109 -0
  50. package/src/commands/push.ts +173 -0
  51. package/src/commands/render.ts +416 -0
  52. package/src/commands/scene.ts +148 -0
  53. package/src/commands/shaders.ts +157 -0
  54. package/src/commands/status.ts +29 -0
  55. package/src/commands/test.ts +371 -0
  56. package/src/commands/thumbs.ts +178 -0
  57. package/src/commands/types.ts +87 -0
  58. package/src/commands/update.ts +190 -0
  59. package/src/compile/assetIcons.ts +214 -0
  60. package/src/compile/collect.ts +46 -0
  61. package/src/compile/collectLocal.ts +37 -0
  62. package/src/compile/designCompile.ts +104 -0
  63. package/src/compile/fonts.ts +156 -0
  64. package/src/compile/headlessBundle.ts +129 -0
  65. package/src/compile/nativeStack.ts +21 -0
  66. package/src/compile/projectCompile.ts +109 -0
  67. package/src/compile/sceneCompile.ts +113 -0
  68. package/src/compile/screenEntry.ts +127 -0
  69. package/src/compile/shaders.ts +245 -0
  70. package/src/config.ts +42 -0
  71. package/src/designMeta.ts +35 -0
  72. package/src/desktopRenderer.ts +545 -0
  73. package/src/desktopScript.ts +276 -0
  74. package/src/dev/clientTemplates.ts +160 -0
  75. package/src/dev/devServer.ts +333 -0
  76. package/src/dev/webRunner.ts +22 -0
  77. package/src/dev/wsServer.ts +144 -0
  78. package/src/distRoot.ts +20 -0
  79. package/src/ignore.ts +163 -0
  80. package/src/index.ts +494 -0
  81. package/src/lecodes-3d-editor.d.ts +41 -0
  82. package/src/lecodes-assets.d.ts +7 -0
  83. package/src/lecodes-design.d.ts +191 -0
  84. package/src/lecodes-renderer.d.ts +131 -0
  85. package/src/localFiles.ts +144 -0
  86. package/src/manifest.ts +46 -0
  87. package/src/matcTool.ts +137 -0
  88. package/src/peers.ts +40 -0
  89. package/src/project.ts +61 -0
  90. package/src/projectEnv.ts +94 -0
  91. package/src/qrcode-terminal.d.ts +9 -0
  92. package/src/releases.ts +125 -0
  93. package/src/serverDiff.ts +78 -0
  94. package/src/textDiff.ts +103 -0
  95. package/src/types.ts +0 -0
  96. package/src/util.ts +146 -0
  97. package/runtime/sdk/animate/animate.ts +0 -238
  98. package/runtime/sdk/animate/bezier.ts +0 -138
  99. package/runtime/sdk/animate/easings.ts +0 -126
  100. package/runtime/sdk/canvas/Canvas.ts +0 -305
  101. package/runtime/sdk/compile/aspectMacro.ts +0 -86
  102. package/runtime/sdk/compile/assetIconMacro.ts +0 -384
  103. package/runtime/sdk/compile/assetMacro.ts +0 -146
  104. package/runtime/sdk/compile/assetName.ts +0 -50
  105. package/runtime/sdk/compile/bundler.ts +0 -287
  106. package/runtime/sdk/compile/compileProject.ts +0 -129
  107. package/runtime/sdk/compile/detectEntry.ts +0 -128
  108. package/runtime/sdk/compile/fontMacro.ts +0 -459
  109. package/runtime/sdk/compile/fontRegistry.ts +0 -78
  110. package/runtime/sdk/compile/header.ts +0 -67
  111. package/runtime/sdk/compile/index.ts +0 -100
  112. package/runtime/sdk/compile/libraryImports.ts +0 -52
  113. package/runtime/sdk/compile/liteMaterial.ts +0 -247
  114. package/runtime/sdk/compile/sceneEditor.ts +0 -88
  115. package/runtime/sdk/compile/serverSplit.ts +0 -233
  116. package/runtime/sdk/compile/serverTypes.ts +0 -227
  117. package/runtime/sdk/compile/sfnt.ts +0 -98
  118. package/runtime/sdk/compile/shaderSchema.ts +0 -202
  119. package/runtime/sdk/compile/shaderTargets.ts +0 -81
  120. package/runtime/sdk/compile/sourcemap.ts +0 -25
  121. package/runtime/sdk/core/Aspect.ts +0 -568
  122. package/runtime/sdk/core/InspectorUI.ts +0 -212
  123. package/runtime/sdk/core/StateMachine.ts +0 -308
  124. package/runtime/sdk/core/color.ts +0 -66
  125. package/runtime/sdk/core/compWrite.ts +0 -42
  126. package/runtime/sdk/core/events.ts +0 -38
  127. package/runtime/sdk/core/fields.ts +0 -120
  128. package/runtime/sdk/core/registry.ts +0 -23
  129. package/runtime/sdk/core/signals.ts +0 -277
  130. package/runtime/sdk/core/time.ts +0 -84
  131. package/runtime/sdk/g2/Camera2D.ts +0 -40
  132. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  133. package/runtime/sdk/g2/Node2D.ts +0 -267
  134. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  135. package/runtime/sdk/g2/Physics2D.ts +0 -346
  136. package/runtime/sdk/g2/Scene2D.ts +0 -209
  137. package/runtime/sdk/g2/Shape2D.ts +0 -259
  138. package/runtime/sdk/g2/Sprite.ts +0 -89
  139. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  140. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  141. package/runtime/sdk/g2/Texture2D.ts +0 -47
  142. package/runtime/sdk/g2/Tilemap.ts +0 -41
  143. package/runtime/sdk/g2/Tileset.ts +0 -71
  144. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  145. package/runtime/sdk/g2/autotile.ts +0 -433
  146. package/runtime/sdk/g2/cells.ts +0 -91
  147. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  148. package/runtime/sdk/g2/groups2d.ts +0 -106
  149. package/runtime/sdk/g2/loop.ts +0 -50
  150. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  151. package/runtime/sdk/g2/touch.ts +0 -83
  152. package/runtime/sdk/gl/Camera.ts +0 -160
  153. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  154. package/runtime/sdk/gl/CharacterController.ts +0 -238
  155. package/runtime/sdk/gl/Gearbox.ts +0 -212
  156. package/runtime/sdk/gl/Geometry.ts +0 -279
  157. package/runtime/sdk/gl/IK.ts +0 -193
  158. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  159. package/runtime/sdk/gl/Light.ts +0 -107
  160. package/runtime/sdk/gl/Lightmap.ts +0 -256
  161. package/runtime/sdk/gl/Material.ts +0 -260
  162. package/runtime/sdk/gl/Mesh.ts +0 -83
  163. package/runtime/sdk/gl/Model.ts +0 -124
  164. package/runtime/sdk/gl/NavAgent.ts +0 -337
  165. package/runtime/sdk/gl/NavMesh.ts +0 -397
  166. package/runtime/sdk/gl/Node.ts +0 -350
  167. package/runtime/sdk/gl/Noise.ts +0 -30
  168. package/runtime/sdk/gl/Particles.ts +0 -676
  169. package/runtime/sdk/gl/Physics.ts +0 -222
  170. package/runtime/sdk/gl/Plane.ts +0 -53
  171. package/runtime/sdk/gl/Ragdoll.ts +0 -270
  172. package/runtime/sdk/gl/Ray.ts +0 -16
  173. package/runtime/sdk/gl/Scene.ts +0 -486
  174. package/runtime/sdk/gl/Shape.ts +0 -409
  175. package/runtime/sdk/gl/Terrain.ts +0 -1092
  176. package/runtime/sdk/gl/Texture.ts +0 -63
  177. package/runtime/sdk/gl/Trigger.ts +0 -45
  178. package/runtime/sdk/gl/Vehicle.ts +0 -473
  179. package/runtime/sdk/gl/Wheel.ts +0 -240
  180. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  181. package/runtime/sdk/gl/animation/Animator.ts +0 -120
  182. package/runtime/sdk/gl/animation/Layer.ts +0 -31
  183. package/runtime/sdk/gl/animation/Loop.ts +0 -30
  184. package/runtime/sdk/gl/animation/Playback.ts +0 -44
  185. package/runtime/sdk/gl/animation/core.ts +0 -336
  186. package/runtime/sdk/gl/controls.ts +0 -95
  187. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  188. package/runtime/sdk/gl/scenarios.ts +0 -291
  189. package/runtime/sdk/gl/state.ts +0 -6
  190. package/runtime/sdk/gl/terrainMesh.ts +0 -219
  191. package/runtime/sdk/gl/touch.ts +0 -68
  192. package/runtime/sdk/inject.ts +0 -197
  193. package/runtime/sdk/math/Mathf.ts +0 -118
  194. package/runtime/sdk/math/mat4.ts +0 -278
  195. package/runtime/sdk/math/quat.ts +0 -232
  196. package/runtime/sdk/math/vec.ts +0 -255
  197. package/runtime/sdk/net/codec.ts +0 -119
  198. package/runtime/sdk/net/core.ts +0 -384
  199. package/runtime/sdk/net/index.ts +0 -181
  200. package/runtime/sdk/net/replication.ts +0 -622
  201. package/runtime/sdk/plugins/camera.ts +0 -81
  202. package/runtime/sdk/plugins/geolocation.ts +0 -123
  203. package/runtime/sdk/plugins/oauth.ts +0 -61
  204. package/runtime/sdk/plugins/permission.ts +0 -7
  205. package/runtime/sdk/plugins/push.ts +0 -132
  206. package/runtime/sdk/plugins/qr.ts +0 -73
  207. package/runtime/sdk/plugins/service.ts +0 -47
  208. package/runtime/sdk/runtime/app.ts +0 -101
  209. package/runtime/sdk/runtime/appEvents.ts +0 -54
  210. package/runtime/sdk/runtime/channel.ts +0 -61
  211. package/runtime/sdk/runtime/clipboard.ts +0 -20
  212. package/runtime/sdk/runtime/datetime.ts +0 -329
  213. package/runtime/sdk/runtime/device.ts +0 -293
  214. package/runtime/sdk/runtime/fetch.ts +0 -77
  215. package/runtime/sdk/runtime/files.ts +0 -23
  216. package/runtime/sdk/runtime/input.ts +0 -175
  217. package/runtime/sdk/runtime/media.ts +0 -111
  218. package/runtime/sdk/runtime/misc.ts +0 -16
  219. package/runtime/sdk/runtime/net.ts +0 -36
  220. package/runtime/sdk/runtime/rpc.ts +0 -218
  221. package/runtime/sdk/runtime/service.ts +0 -83
  222. package/runtime/sdk/runtime/share.ts +0 -9
  223. package/runtime/sdk/runtime/storage.ts +0 -13
  224. package/runtime/sdk/runtime/touch.ts +0 -76
  225. package/runtime/sdk/scene/defineScene.ts +0 -1353
  226. package/runtime/sdk/scene/editorPlugins.ts +0 -110
  227. package/runtime/sdk/scene/gizmos.ts +0 -148
  228. package/runtime/sdk/scene/grammar.ts +0 -120
  229. package/runtime/sdk/scene/material.ts +0 -188
  230. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  231. package/runtime/sdk/server/auth/global.ts +0 -80
  232. package/runtime/sdk/server/auth/host.ts +0 -318
  233. package/runtime/sdk/server/auth/models.ts +0 -83
  234. package/runtime/sdk/server/auth/types.ts +0 -50
  235. package/runtime/sdk/server/channel.ts +0 -56
  236. package/runtime/sdk/server/context.ts +0 -36
  237. package/runtime/sdk/server/db/defineDb.ts +0 -237
  238. package/runtime/sdk/server/db/fields.ts +0 -132
  239. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  240. package/runtime/sdk/server/db/index.ts +0 -7
  241. package/runtime/sdk/server/db/marci/query.ts +0 -412
  242. package/runtime/sdk/server/db/types.ts +0 -202
  243. package/runtime/sdk/server/errors.ts +0 -12
  244. package/runtime/sdk/server/host.ts +0 -74
  245. package/runtime/sdk/server/inject.ts +0 -13
  246. package/runtime/sdk/server/runtime.ts +0 -133
  247. package/runtime/sdk/server/validate.ts +0 -87
  248. package/runtime/sdk/ui/NativeView.ts +0 -142
  249. package/runtime/sdk/ui/UI.ts +0 -39
  250. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  251. package/runtime/sdk/ui/UIButton.ts +0 -101
  252. package/runtime/sdk/ui/UIContainer.ts +0 -60
  253. package/runtime/sdk/ui/UIImage.ts +0 -83
  254. package/runtime/sdk/ui/UIInput.ts +0 -185
  255. package/runtime/sdk/ui/UIModal.ts +0 -139
  256. package/runtime/sdk/ui/UINode.ts +0 -830
  257. package/runtime/sdk/ui/UIPager.ts +0 -362
  258. package/runtime/sdk/ui/UIPopover.ts +0 -100
  259. package/runtime/sdk/ui/UIScreen.ts +0 -123
  260. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  261. package/runtime/sdk/ui/UISpacer.ts +0 -14
  262. package/runtime/sdk/ui/UITabs.ts +0 -236
  263. package/runtime/sdk/ui/UIText.ts +0 -51
  264. package/runtime/sdk/ui/UIVideo.ts +0 -88
  265. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  266. package/runtime/sdk/ui/UIWidget.ts +0 -127
  267. package/runtime/sdk/ui/fonts.ts +0 -13
  268. package/runtime/sdk/ui/presentable.ts +0 -117
  269. package/runtime/sdk/ui/router.ts +0 -132
  270. package/runtime/sdk/ui/theme.ts +0 -84
@@ -1,433 +0,0 @@
1
- // Autotile materials: the pure model + deriver behind `.tiles.ts` files (docs/scene2d-editor-plan.md
2
- // §7). A map painted with MATERIAL ids (intent) is turned into atlas tile indices (display) by
3
- // neighbor-mask rules — never the other way around, and the derived indices are never persisted.
4
- //
5
- // Two material kinds, differing only in their neighbor classifier:
6
- // - 'patch' — area materials (grass, dirt, water, platformer ground): the 8-neighbor blob mask.
7
- // Minimal slot set = fill (+ weighted scatter variants) + 4 edges + 4 outer corners + 4 inner
8
- // corners; missing slots fall back to simpler ones, so a partial set always renders.
9
- // `size: 2 | 3` makes the material live on a size×size BLOCK lattice (anchored at map 0,0):
10
- // classification runs block-to-block, fill holds size² tiles and each edge `size` tiles, while
11
- // corners stay one cell. That is what art with a multi-cell pattern phase needs (herringbone,
12
- // lane dashes), and it makes a one-block-wide strip carry BOTH edges by construction — a
13
- // 2-lane road with its curbs and centre line is just `size: 2` painted one block wide.
14
- // - 'path' — line materials (roads, fences, walls, rails): the 4-neighbor mask, 16 classes
15
- // (isolated / ends / straights / corners / tees / cross), same fallback principle.
16
- //
17
- // Direction conventions (data is row-major, row 0 = the TOP row): N = row-1, S = row+1, E = col+1,
18
- // W = col-1. Edge arrays are [n, e, s, w]; corner arrays are [ne, se, sw, nw] (clockwise from
19
- // north-east). Cells outside the map count as SAME material, so patches continue off-map instead
20
- // of growing a border along the map edge.
21
- //
22
- // A cell holds a material id, a part id, -1 for empty, or — below -1 — a RAW atlas tile
23
- // (`rawCell(index)`), so hand-placed art can sit in a material map without colliding with ids.
24
- //
25
- // Any atlas index (a raw cell, a slot value) may carry an ORIENTATION packed into the same int:
26
- // `rot90(i)` / `flipX(i)` — see the oriented-tiles section. The deriver treats oriented values as
27
- // opaque positive ints and copies them through; renderers decode the bits into a UV permutation.
28
- //
29
- // Pure and engine-free on purpose — the 2D editor keeps a mirrored copy
30
- // (packages/editor-2d/src/autotile.ts, like cells.ts / history.ts): keep them in sync.
31
-
32
- /** A fill entry: an atlas index, or [index, weight] for seeded scatter variants. */
33
- export type FillEntry = number | [number, number]
34
-
35
- export type Dir4 = [number, number, number, number]
36
-
37
- /** One side's tiles: a single index (used for every cell along that side), or — with `size` > 1 —
38
- * the `size` indices ALONG it (n/s run left→right, w/e run top→bottom). */
39
- export type EdgeSlot = number | number[]
40
-
41
- /** A whole `size`×`size` piece: one index (drawn in every cell of the block), or size² indices
42
- * row-major. Path pieces and parts are blocks — at size 1 they are just a tile. */
43
- export type TileBlock = number | number[]
44
-
45
- /** `size`² indices, row-major within the block. */
46
- export type FillBlock = number[]
47
-
48
- /** A named element placed BY HAND (a pedestrian crossing in a road, a gate in a fence). It carries
49
- * its own `id`, so the map stores it like any material — but it CONNECTS as its parent, so the
50
- * run continues straight through it. Never picked at random: you paint it from the palette, ONE
51
- * CELL at a time, and the tile is worked out from where that cell sits. */
52
- export type TilePart = {
53
- /** Stable small int, unique across every material and part of the tileset. */
54
- id: number
55
- /** Which run the art is drawn for — 'v' vertical, 'h' horizontal. It also picks the art: the
56
- * element varies ACROSS the line (a crossing has one tile per lane) and repeats along it, so
57
- * `tiles` is `size` long, indexed by row for 'h' and by column for 'v'. */
58
- axis?: "v" | "h"
59
- /** The tiles — one index, `size` across the run when `axis` is set, else the size² block. */
60
- tiles?: TileBlock
61
- }
62
-
63
- /** A block variant: the block, or [block, weight] for seeded scatter. */
64
- export type BlockEntry = FillBlock | [FillBlock, number]
65
-
66
- /** One transition set of a patch material — the tiles its border shows against a neighbor. */
67
- export type PatchSlots = {
68
- /** Edge line tiles [n, e, s, w] — e.g. `edges[0]` draws where the patch stops toward the top. */
69
- edges?: [EdgeSlot, EdgeSlot, EdgeSlot, EdgeSlot]
70
- /** Convex corner tiles [ne, se, sw, nw] — the patch's outside corners. Always one cell. */
71
- outer?: Dir4
72
- /** Concave corner tiles [ne, se, sw, nw] — the notch where two patch arms meet. One cell. */
73
- inner?: Dir4
74
- }
75
-
76
- export type PatchMaterial = {
77
- /** Stable small int — what `cells('…')` stores when a map carries Autotile2D. */
78
- id: number
79
- kind: "patch"
80
- /** Editor swatch color ('#4a7c3a'). */
81
- color?: string
82
- /** Block size in cells (default 1). With 2 or 3 the material is classified block-to-block on a
83
- * lattice anchored at the map origin, `fill` holds size² tiles and each edge `size` tiles. */
84
- size?: number
85
- /** Interior tile — one index, or an ARRAY of scatter variants (each `index` or
86
- * `[index, weight]`; bare indices weigh 1): `fill: [261, [262, 0.1], [263, 0.05]]`.
87
- * With `size` > 1 it is instead the row-major BLOCK of size² indices
88
- * (`fill: [130, 131, 146, 147]`), or a list of such blocks for scatter variants. */
89
- fill: number | FillEntry[] | BlockEntry[]
90
- /** Transition sets keyed by the neighboring material's name; '*' = any other neighbor
91
- * (including empty). Baked packs draw a material fading into a SPECIFIC background. */
92
- over?: Record<string, PatchSlots>
93
- /** Hand-placed elements that connect as this material (see TilePart). */
94
- parts?: Record<string, TilePart>
95
- }
96
-
97
- export type PathMaterial = {
98
- id: number
99
- kind: "path"
100
- color?: string
101
- /** Cells ACROSS the line (default 1). A `size: 2` road is two lanes wide: connectivity runs
102
- * block to block and every piece below is a 2×2 block instead of one tile. */
103
- size?: number
104
- /** No connections at all. */
105
- isolated?: TileBlock
106
- /** End caps [n, e, s, w] — `ends[0]` connects toward the top only. */
107
- ends?: [TileBlock, TileBlock, TileBlock, TileBlock]
108
- /** [vertical (n+s), horizontal (e+w)]. */
109
- straights?: [TileBlock, TileBlock]
110
- /** Elbows [ne, se, sw, nw] — `corners[0]` connects top + right. */
111
- corners?: [TileBlock, TileBlock, TileBlock, TileBlock]
112
- /** T pieces [n, e, s, w] — `tees[0]` is the tee MISSING the top connection. */
113
- tees?: [TileBlock, TileBlock, TileBlock, TileBlock]
114
- /** All four connections. */
115
- cross?: TileBlock
116
- /** Hand-placed elements that connect as this material (see TilePart). */
117
- parts?: Record<string, TilePart>
118
- }
119
-
120
- export type TileMaterial = PatchMaterial | PathMaterial
121
-
122
- export type TilesetDef = {
123
- /** The atlas image — write `asset('./TileSet.png')`. */
124
- image: string
125
- /** Atlas cell size in image pixels — a number (square) or [w, h]. */
126
- tile: number | [number, number]
127
- /** Named materials; names are the editor/`over:` vocabulary, `id`s are what maps store. */
128
- materials?: Record<string, TileMaterial>
129
- }
130
-
131
- // ---- helpers -------------------------------------------------------------------
132
-
133
- export const normalizeFill = (fill: number | FillEntry[]): [number, number][] => {
134
- if (typeof fill === "number") return [ [ fill, 1 ] ]
135
- return fill.map((e) => (typeof e === "number" ? [ e, 1 ] : [ e[0], e[1] ]))
136
- }
137
-
138
- /** A material's block size — 1 unless it declares `size` (clamped to a positive int). */
139
- export const blockSize = (mat: TileMaterial): number => Math.max(1, Math.round(mat.size ?? 1))
140
-
141
- /** One cell of a block: a scalar covers the whole block, an array reads row-major. Undefined =
142
- * unassigned (missing, or the negative sentinel the editor writes for a blank slot). */
143
- export const blockTile = (
144
- block: TileBlock | undefined, n: number, lx: number, ly: number,
145
- ): number | undefined => {
146
- if (typeof block === "number") return block >= 0 ? block : undefined
147
- if (!Array.isArray(block)) return undefined
148
- const t = block[ly * n + lx]
149
- return typeof t === "number" && t >= 0 ? t : undefined
150
- }
151
-
152
- /** How many tiles a part holds: `size` across the run when it declares an axis, else the block. */
153
- export const partSlots = (part: TilePart, n: number): number => (part.axis ? n : n * n)
154
-
155
- // ---- raw tiles inside a material map ------------------------------------------------------------
156
- // A material map stores INTENT, so a cell holding a plain atlas index would be ambiguous (is `3`
157
- // material 3 or tile 3?). Raw tiles therefore live below -1, out of the material id space: -2 is
158
- // tile 0, -3 is tile 1, and so on. -1 stays empty, and a raw cell simply draws itself.
159
-
160
- /** The cell value that draws atlas tile `index` verbatim in a material map. */
161
- export const rawCell = (index: number): number => -index - 2
162
-
163
- /** True for a cell holding a raw atlas tile rather than a material id. */
164
- export const isRawCell = (v: number): boolean => v <= -2
165
-
166
- /** The atlas index behind a raw cell (garbage in, garbage out — guard with isRawCell). */
167
- export const rawIndex = (v: number): number => -v - 2
168
-
169
- // ---- oriented tiles -----------------------------------------------------------------------------
170
- // Any tile value — a slot in a `.tiles.ts` material, a raw cell, a plain index in a raw map — can
171
- // carry an orientation in the SAME int: bits 0–27 hold the atlas index, bit 28 a horizontal mirror,
172
- // bits 29–30 the clockwise quarter-turns; the mirror applies FIRST, then the rotation. The sign bit
173
- // is never touched, so every oriented value stays positive: -1 (empty), raw cells (≤ -2) and the
174
- // negative "unassigned slot" sentinel all keep working, and the deriver passes oriented values
175
- // through untouched. Renderers permute the atlas cell's UV corners — same quads, same batches.
176
- // `rot90`/`flipX` COMPOSE (they act on the value's current orientation), so `rot90(rot90(t))` is a
177
- // half turn and all 8 orientations of a tile are reachable; write them in `.tiles.ts` files
178
- // directly: `ends: [121, rot90(121), rot180(121), rot270(121)]`.
179
-
180
- export const TILE_INDEX_MASK = 0x0fffffff
181
- const TILE_FLIP = 1 << 28
182
-
183
- /** The atlas index behind a (possibly oriented) tile value. */
184
- export const tileIndex = (v: number): number => v & TILE_INDEX_MASK
185
- /** Clockwise quarter-turns (0–3) of a tile value. */
186
- export const tileTurns = (v: number): number => (v >> 29) & 3
187
- /** Whether the tile is mirrored horizontally (the mirror applies before the rotation). */
188
- export const tileFlip = (v: number): boolean => (v & TILE_FLIP) !== 0
189
- /** True when the value carries any orientation (renderers keep the fast path otherwise). */
190
- export const tileOriented = (v: number): boolean => v > TILE_INDEX_MASK
191
- /** Build an oriented tile value: `index`, mirrored when `flip`, then `turns` quarter-turns CW. */
192
- export const packTile = (index: number, turns = 0, flip = false): number =>
193
- (index & TILE_INDEX_MASK) | (flip ? TILE_FLIP : 0) | ((turns & 3) << 29)
194
-
195
- /** The value rotated a further 90° clockwise. */
196
- export const rot90 = (v: number): number => packTile(tileIndex(v), tileTurns(v) + 1, tileFlip(v))
197
- /** The value rotated a further 180°. */
198
- export const rot180 = (v: number): number => packTile(tileIndex(v), tileTurns(v) + 2, tileFlip(v))
199
- /** The value rotated a further 270° clockwise (90° counter-clockwise). */
200
- export const rot270 = (v: number): number => packTile(tileIndex(v), tileTurns(v) + 3, tileFlip(v))
201
- /** The value mirrored horizontally (on screen — existing turns are re-based, group math). */
202
- export const flipX = (v: number): number => packTile(tileIndex(v), 4 - tileTurns(v), !tileFlip(v))
203
-
204
- /** The tile a part draws in a cell — indexed ACROSS the run ('h' by row, 'v' by column) so one
205
- * element covers a lane each and repeats along the run; without an axis it is a plain block. */
206
- export const partTile = (part: TilePart, n: number, lx: number, ly: number): number | undefined => {
207
- const tiles = part.tiles
208
- if (typeof tiles === "number") return tiles >= 0 ? tiles : undefined
209
- if (!Array.isArray(tiles)) return undefined
210
- const t = tiles[part.axis === "h" ? ly : part.axis === "v" ? lx : ly * n + lx]
211
- return typeof t === "number" && t >= 0 ? t : undefined
212
- }
213
-
214
- /** `fill` as weighted BLOCKS of `size`² indices (row-major). Size 1 keeps the scalar/scatter
215
- * reading; with size > 1 a flat number array is one positional block, an array of arrays is the
216
- * variant list, and a bare number fills the whole block. Missing entries read as -1. */
217
- export const fillBlocks = (fill: PatchMaterial["fill"], size = 1): [number[], number][] => {
218
- const n = Math.max(1, Math.round(size))
219
- const area = n * n
220
- const pad = (b: readonly number[]): number[] =>
221
- Array.from({ length: area }, (_, i) => (typeof b[i] === "number" ? b[i]! : -1))
222
- if (typeof fill === "number") return [ [ new Array<number>(area).fill(fill), 1 ] ]
223
- if (!Array.isArray(fill) || fill.length === 0) return []
224
- if (n === 1) return normalizeFill(fill as FillEntry[]).map(([ i, w ]) => [ [ i ], w ])
225
- if (!Array.isArray(fill[0])) return [ [ pad(fill as number[]), 1 ] ]
226
- return (fill as BlockEntry[]).map((e): [number[], number] =>
227
- Array.isArray(e[0]) ? [ pad(e[0] as number[]), (e as [FillBlock, number])[1] ?? 1 ] : [ pad(e as number[]), 1 ])
228
- }
229
-
230
- /** Deterministic per-cell hash in [0, 1) — scatter must not reshuffle when nearby cells change. */
231
- const hash01 = (x: number, y: number, seed: number): number => {
232
- let h = (Math.imul(x, 374761393) + Math.imul(y, 668265263) + Math.imul(seed, 2246822519)) | 0
233
- h = Math.imul(h ^ (h >>> 13), 1274126177)
234
- h ^= h >>> 16
235
- return (h >>> 0) / 4294967296
236
- }
237
-
238
- /** The fill tile for block (bc, br) at block-local (lx, ly). Scatter rolls once per BLOCK, so a
239
- * multi-cell pattern never tears; at size 1 the block IS the cell (identical to before). */
240
- const pickFill = (
241
- mat: PatchMaterial, bc: number, br: number, seed: number, n: number, lx: number, ly: number,
242
- ): number => {
243
- const variants = fillBlocks(mat.fill, n)
244
- if (variants.length === 0) return -1
245
- const at = ly * n + lx
246
- if (variants.length === 1) return variants[0]![0][at] ?? -1
247
- const total = variants.reduce((s, v) => s + v[1], 0)
248
- let roll = hash01(bc, br, seed + mat.id * 7919) * total
249
- for (const [ block, w ] of variants) {
250
- roll -= w
251
- if (roll < 0) return block[at] ?? -1
252
- }
253
- return variants[variants.length - 1]![0][at] ?? -1
254
- }
255
-
256
- // corner order [ne, se, sw, nw] as (dx, dy) pairs; edge order [n, e, s, w]
257
- const EDGE_D: [number, number][] = [ [ 0, -1 ], [ 1, 0 ], [ 0, 1 ], [ -1, 0 ] ]
258
- const CORNER_D: [number, number][] = [ [ 1, -1 ], [ 1, 1 ], [ -1, 1 ], [ -1, -1 ] ]
259
-
260
- // ---- the deriver ---------------------------------------------------------------
261
-
262
- /**
263
- * Derive display atlas indices from a material-id grid (row-major, row 0 = top, -1 = empty).
264
- * Values that match no material id pass through unchanged (so converting maps degrades
265
- * gracefully); -1 stays -1. Deterministic: same inputs + seed → same output on every platform.
266
- */
267
- export const deriveCells = (
268
- def: TilesetDef, cols: number, rows: number, cells: ArrayLike<number>, seed = 0,
269
- ): Int32Array => {
270
- const byId = new Map<number, { name: string, mat: TileMaterial }>()
271
- const parts = new Map<number, { part: TilePart, mat: TileMaterial, owner: number }>()
272
- for (const [ name, mat ] of Object.entries(def.materials ?? {})) {
273
- if (!byId.has(mat.id)) byId.set(mat.id, { name, mat })
274
- for (const part of Object.values(mat.parts ?? {})) {
275
- if (!parts.has(part.id)) parts.set(part.id, { part, mat, owner: mat.id })
276
- }
277
- }
278
- // a part's cells read as their PARENT everywhere connectivity is asked — the run continues
279
- // straight through a crossing or a gate; only the drawing differs
280
- const ownerOf = (v: number): number => parts.get(v)?.owner ?? v
281
-
282
- const out = new Int32Array(cols * rows)
283
- const at = (c: number, r: number, self: number): number =>
284
- (c < 0 || r < 0 || c >= cols || r >= rows) ? self : ownerOf(cells[r * cols + c] as number)
285
- /** A block belongs to `self` when ANY of its cells does — so a block you painted one cell at a
286
- * time still reads as part of the run (and off-map keeps counting as same). */
287
- const blockAt = (ax: number, ay: number, n: number, self: number): number => {
288
- if (n === 1) return at(ax, ay, self)
289
- for (let y = ay; y < ay + n; y++) {
290
- for (let x = ax; x < ax + n; x++) if (at(x, y, self) === self) return self
291
- }
292
- return at(ax, ay, self)
293
- }
294
-
295
- for (let r = 0; r < rows; r++) {
296
- for (let c = 0; c < cols; c++) {
297
- const raw = cells[r * cols + c] as number
298
- // a raw cell draws its atlas tile verbatim — hand-placed art inside a material map
299
- if (isRawCell(raw)) { out[r * cols + c] = rawIndex(raw); continue }
300
- const v = raw >= 0 ? ownerOf(raw) : raw
301
- const entry = v >= 0 ? byId.get(v) : undefined
302
- if (!entry) { out[r * cols + c] = raw; continue }
303
- const hit = parts.get(raw)
304
- if (hit) {
305
- const n = blockSize(entry.mat)
306
- const t = partTile(hit.part, n, c - Math.floor(c / n) * n, r - Math.floor(r / n) * n)
307
- // an unassigned part still draws its material, so a half-authored element never blanks out
308
- if (t !== undefined) { out[r * cols + c] = t; continue }
309
- }
310
- out[r * cols + c] = entry.mat.kind === "patch"
311
- ? derivePatch(entry.mat, byId, blockAt, c, r, v, seed)
312
- : derivePath(entry.mat, blockAt, c, r, v)
313
- }
314
- }
315
- return out
316
- }
317
-
318
- /** Convex corners as [cardA, cardB, cornerIndex]: n+e → ne(0), e+s → se(1), s+w → sw(2), w+n → nw(3). */
319
- const CORNER_PAIRS: [number, number, number][] = [ [ 0, 1, 0 ], [ 1, 2, 1 ], [ 2, 3, 2 ], [ 3, 0, 3 ] ]
320
-
321
- /** Where each concave corner shows inside a block: [localX, localY, cardA, cardB], in [ne, se, sw, nw]. */
322
- const INNER_AT = (n: number): [number, number, number, number][] => [
323
- [ n - 1, 0, 0, 1 ], [ n - 1, n - 1, 1, 2 ], [ 0, n - 1, 2, 3 ], [ 0, 0, 3, 0 ],
324
- ]
325
-
326
- /** Samples a whole BLOCK: (anchorX, anchorY, size, self) → the material that block belongs to. */
327
- type BlockAt = (ax: number, ay: number, n: number, self: number) => number
328
-
329
- const derivePatch = (
330
- mat: PatchMaterial, byId: Map<number, { name: string, mat: TileMaterial }>,
331
- at: BlockAt, c: number, r: number, v: number, seed: number,
332
- ): number => {
333
- // With `size` > 1 the material lives on a block lattice anchored at the map origin: neighbors are
334
- // sampled one BLOCK away (at that block's anchor cell) and the tile is placed by the cell's
335
- // position INSIDE the block. n = 1 makes every block one cell — the original per-cell rules.
336
- const n = blockSize(mat)
337
- const bc = Math.floor(c / n), br = Math.floor(r / n)
338
- const lx = c - bc * n, ly = r - br * n
339
- const ax = bc * n, ay = br * n
340
- const same = (dx: number, dy: number): boolean => at(ax + dx * n, ay + dy * n, n, v) === v
341
- // cardinal + diagonal same-bits (diagonals only matter where both adjacent cardinals are same)
342
- const card = EDGE_D.map(([ dx, dy ]) => same(dx, dy)) // [n, e, s, w]
343
- const diag = CORNER_D.map(([ dx, dy ]) => same(dx, dy)) // [ne, se, sw, nw]
344
- const fill = (): number => pickFill(mat, bc, br, seed, n, lx, ly)
345
-
346
- const slots = pickSlots(mat, byId, at, ax, ay, v, n)
347
- if (!slots) return fill()
348
- // a negative value inside a slot array means "unassigned" (the editor writes partial sets)
349
- const pick = (t: number | undefined): number | undefined => (t !== undefined && t >= 0 ? t : undefined)
350
- const outer = (i: number): number | undefined => pick(slots.outer?.[i])
351
- const edge = (d: number): number | undefined => {
352
- const s = slots.edges?.[d]
353
- if (typeof s === "number") return pick(s)
354
- return Array.isArray(s) ? pick(s[d === 0 || d === 2 ? lx : ly]) : undefined
355
- }
356
-
357
- // sides of THIS cell that sit on a block border facing another material
358
- const on = [
359
- !card[0]! && ly === 0, !card[1]! && lx === n - 1, !card[2]! && ly === n - 1, !card[3]! && lx === 0,
360
- ]
361
- for (const [ a, b, i ] of CORNER_PAIRS) {
362
- if (on[a]! && on[b]!) { const t = outer(i); if (t !== undefined) return t }
363
- }
364
- for (let d = 0; d < 4; d++) if (on[d]!) { const t = edge(d); if (t !== undefined) return t }
365
- if (on.some(Boolean)) return fill()
366
-
367
- // block interior — a differing relevant diagonal is a concave (inner) corner, drawn in the one
368
- // cell of the block that touches it
369
- const inner = INNER_AT(n)
370
- for (let i = 0; i < 4; i++) {
371
- const [ px, py, a, b ] = inner[i]!
372
- if (lx === px && ly === py && card[a]! && card[b]! && !diag[i]!) return pick(slots.inner?.[i]) ?? fill()
373
- }
374
- return fill()
375
- }
376
-
377
- /** The transition set for this block: keyed by the dominant differing neighbor's material name,
378
- * '*' as the fallback (empty and unknown neighbors always land on '*'). */
379
- const pickSlots = (
380
- mat: PatchMaterial, byId: Map<number, { name: string, mat: TileMaterial }>,
381
- at: BlockAt, ax: number, ay: number, v: number, n: number,
382
- ): PatchSlots | undefined => {
383
- const over = mat.over
384
- if (!over) return undefined
385
- const counts = new Map<number, number>()
386
- for (const [ dx, dy ] of [ ...EDGE_D, ...CORNER_D ]) {
387
- const nv = at(ax + dx * n, ay + dy * n, n, v)
388
- if (nv !== v) counts.set(nv, (counts.get(nv) ?? 0) + 1)
389
- }
390
- let bestId: number | null = null, best = 0
391
- for (const [ id, n ] of counts) {
392
- if (n > best || (n === best && bestId !== null && id < bestId)) { best = n; bestId = id }
393
- }
394
- const name = bestId !== null && bestId >= 0 ? byId.get(bestId)?.name : undefined
395
- return (name !== undefined ? over[name] : undefined) ?? over["*"]
396
- }
397
-
398
- const derivePath = (
399
- mat: PathMaterial, at: BlockAt, c: number, r: number, v: number,
400
- ): number => {
401
- // `size` makes the line that many cells ACROSS: connectivity is block to block and every piece
402
- // is a size² block placed by the cell's position inside it (size 1 = one tile per piece).
403
- const n = blockSize(mat)
404
- const bc = Math.floor(c / n), br = Math.floor(r / n)
405
- const lx = c - bc * n, ly = r - br * n
406
- const ax = bc * n, ay = br * n
407
- const conn = EDGE_D.map(([ dx, dy ]) => at(ax + dx * n, ay + dy * n, n, v) === v) // [n, e, s, w]
408
- const links = conn.filter(Boolean).length
409
- // a negative value inside a piece means "unassigned" (the editor writes partial sets)
410
- const pick = (b: TileBlock | undefined): number | undefined => blockTile(b, n, lx, ly)
411
- const any = (): number =>
412
- pick(mat.isolated) ?? pick(mat.cross) ?? pick(mat.straights?.[1]) ?? pick(mat.straights?.[0])
413
- ?? mat.ends?.map((b) => pick(b)).find((t) => t !== undefined)
414
- ?? pick(mat.corners?.[0]) ?? pick(mat.tees?.[0]) ?? v
415
-
416
- if (links === 0) return pick(mat.isolated) ?? any()
417
- if (links === 1) {
418
- const d = conn.findIndex(Boolean)
419
- return pick(mat.ends?.[d]) ?? pick(mat.straights?.[d % 2 === 0 ? 0 : 1]) ?? any()
420
- }
421
- if (links === 2) {
422
- if (conn[0] && conn[2]) return pick(mat.straights?.[0]) ?? any()
423
- if (conn[1] && conn[3]) return pick(mat.straights?.[1]) ?? any()
424
- // elbows: n+e → ne(0), e+s → se(1), s+w → sw(2), w+n → nw(3)
425
- const corner = conn[0] && conn[1] ? 0 : conn[1] && conn[2] ? 1 : conn[2] && conn[3] ? 2 : 3
426
- return pick(mat.corners?.[corner]) ?? pick(mat.cross) ?? any()
427
- }
428
- if (links === 3) {
429
- const missing = conn.findIndex((s) => !s)
430
- return pick(mat.tees?.[missing]) ?? pick(mat.cross) ?? pick(mat.straights?.[missing % 2 === 0 ? 1 : 0]) ?? any()
431
- }
432
- return pick(mat.cross) ?? any()
433
- }
@@ -1,91 +0,0 @@
1
- // The `cells('…')` payload literal used by `.scene2d.ts` tilemap blocks (and terrain material maps
2
- // later): one opaque base64 string holding the grid dimensions + row-major Int32 cell values, so a
3
- // whole map prints on one line of the scene file. Layout (little-endian Int32): [cols, rows,
4
- // runLen₁, value₁, runLen₂, value₂, …] — run-length pairs, because tilemaps are dominated by long
5
- // runs of the same tile (a 100×100 grass field is a handful of pairs, not 10k ints). The payload is
6
- // versionless by design: cols is always > 0, so a future format can claim a negative sentinel.
7
- //
8
- // Self-contained on purpose — QuickJS has no atob/Buffer, and the editor keeps its own mirrored
9
- // copy of this codec (the scene-doc/editor side must not depend on the SDK).
10
-
11
- export type CellsData = {
12
- cols: number
13
- rows: number
14
- /** Row-major tile values (row 0 = the top row), length cols*rows. */
15
- data: Int32Array
16
- }
17
-
18
- const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
19
- let B64_REV: Int8Array | null = null
20
-
21
- const b64encode = (bytes: Uint8Array): string => {
22
- let out = ""
23
- for (let i = 0; i < bytes.length; i += 3) {
24
- const b0 = bytes[i]!, b1 = bytes[i + 1], b2 = bytes[i + 2]
25
- out += B64[b0 >> 2]! + B64[((b0 & 3) << 4) | ((b1 ?? 0) >> 4)]!
26
- out += b1 === undefined ? "=" : B64[((b1 & 15) << 2) | ((b2 ?? 0) >> 6)]!
27
- out += b2 === undefined ? "=" : B64[b2 & 63]!
28
- }
29
- return out
30
- }
31
-
32
- const b64decode = (s: string): Uint8Array => {
33
- if (!B64_REV) {
34
- B64_REV = new Int8Array(128).fill(-1)
35
- for (let i = 0; i < 64; i++) B64_REV[B64.charCodeAt(i)] = i
36
- }
37
- const clean = s.replace(/=+$/, "")
38
- const out = new Uint8Array(Math.floor(clean.length * 3 / 4))
39
- let acc = 0, bits = 0, w = 0
40
- for (let i = 0; i < clean.length; i++) {
41
- const v = B64_REV[clean.charCodeAt(i)] ?? -1
42
- if (v < 0) throw new Error("cells(): payload is not base64")
43
- acc = (acc << 6) | v
44
- bits += 6
45
- if (bits >= 8) { bits -= 8; out[w++] = (acc >> bits) & 0xff }
46
- }
47
- return out
48
- }
49
-
50
- /** Encode a grid into a `cells()` payload (the editor / tools side of the codec). */
51
- export const encodeCells = (cols: number, rows: number, data: ArrayLike<number>): string => {
52
- if (data.length !== cols * rows) throw new Error(`encodeCells: expected ${cols * rows} values, got ${data.length}`)
53
- const ints: number[] = [ cols, rows ]
54
- for (let i = 0; i < data.length;) {
55
- const v = data[i]!
56
- let run = 1
57
- while (i + run < data.length && data[i + run] === v) run++
58
- ints.push(run, v)
59
- i += run
60
- }
61
- const bytes = new Uint8Array(ints.length * 4)
62
- const dv = new DataView(bytes.buffer)
63
- ints.forEach((n, i) => dv.setInt32(i * 4, n, true))
64
- return b64encode(bytes)
65
- }
66
-
67
- /**
68
- * Decode a `cells('…')` payload into the grid a tilemap block consumes. Scene files call this via
69
- * the injected global; the value is plain data, so hand-written code may pass any
70
- * `{ cols, rows, data }` of its own instead (procedural maps).
71
- */
72
- export const cells = (payload: string): CellsData => {
73
- const bytes = b64decode(payload)
74
- if (bytes.length < 16 || bytes.length % 4 !== 0) throw new Error("cells(): malformed payload")
75
- const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
76
- const cols = dv.getInt32(0, true)
77
- const rows = dv.getInt32(4, true)
78
- const count = bytes.length / 4 - 2
79
- if (cols <= 0 || rows <= 0 || count % 2 !== 0) throw new Error("cells(): malformed payload")
80
- const data = new Int32Array(cols * rows)
81
- let w = 0
82
- for (let i = 0; i < count; i += 2) {
83
- const run = dv.getInt32(8 + i * 4, true)
84
- const value = dv.getInt32(12 + i * 4, true)
85
- if (run <= 0 || w + run > data.length) throw new Error("cells(): payload does not match its dimensions")
86
- data.fill(value, w, w + run)
87
- w += run
88
- }
89
- if (w !== data.length) throw new Error("cells(): payload does not match its dimensions")
90
- return { cols, rows, data }
91
- }