@rpgjs/map-builder 0.0.0-stage → 5.0.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 (287) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +341 -2
  3. package/dist/authority/authority.d.ts +118 -0
  4. package/dist/authority/authority.d.ts.map +1 -0
  5. package/dist/authority/authority.js +289 -0
  6. package/dist/authority/authority.js.map +1 -0
  7. package/dist/authority/index.d.ts +3 -0
  8. package/dist/authority/index.d.ts.map +1 -0
  9. package/dist/authority/index.js +3 -0
  10. package/dist/authority/store.d.ts +36 -0
  11. package/dist/authority/store.d.ts.map +1 -0
  12. package/dist/authority/store.js +35 -0
  13. package/dist/authority/store.js.map +1 -0
  14. package/dist/client/index.d.ts +72 -0
  15. package/dist/client/index.d.ts.map +1 -0
  16. package/dist/client/index.js +200 -0
  17. package/dist/client/index.js.map +1 -0
  18. package/dist/embed/client.d.ts +85 -0
  19. package/dist/embed/client.d.ts.map +1 -0
  20. package/dist/embed/client.js +255 -0
  21. package/dist/embed/client.js.map +1 -0
  22. package/dist/embed/host.d.ts +112 -0
  23. package/dist/embed/host.d.ts.map +1 -0
  24. package/dist/embed/host.js +278 -0
  25. package/dist/embed/host.js.map +1 -0
  26. package/dist/embed/index.d.ts +4 -0
  27. package/dist/embed/index.d.ts.map +1 -0
  28. package/dist/embed/index.js +4 -0
  29. package/dist/embed/protocol.d.ts +137 -0
  30. package/dist/embed/protocol.d.ts.map +1 -0
  31. package/dist/embed/protocol.js +161 -0
  32. package/dist/embed/protocol.js.map +1 -0
  33. package/dist/features/index.d.ts +66 -0
  34. package/dist/features/index.d.ts.map +1 -0
  35. package/dist/features/index.js +157 -0
  36. package/dist/features/index.js.map +1 -0
  37. package/dist/features/skin.d.ts +9 -0
  38. package/dist/features/skin.d.ts.map +1 -0
  39. package/dist/features/skin.js +43 -0
  40. package/dist/features/skin.js.map +1 -0
  41. package/dist/features/terrain.d.ts +66 -0
  42. package/dist/features/terrain.d.ts.map +1 -0
  43. package/dist/features/terrain.js +291 -0
  44. package/dist/features/terrain.js.map +1 -0
  45. package/dist/index.d.ts +9 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +51 -0
  48. package/dist/interaction/builtin-intents.d.ts +9 -0
  49. package/dist/interaction/builtin-intents.d.ts.map +1 -0
  50. package/dist/interaction/builtin-intents.js +61 -0
  51. package/dist/interaction/builtin-intents.js.map +1 -0
  52. package/dist/interaction/catalog.d.ts +24 -0
  53. package/dist/interaction/catalog.d.ts.map +1 -0
  54. package/dist/interaction/catalog.js +20 -0
  55. package/dist/interaction/catalog.js.map +1 -0
  56. package/dist/interaction/controller.d.ts +137 -0
  57. package/dist/interaction/controller.d.ts.map +1 -0
  58. package/dist/interaction/controller.js +215 -0
  59. package/dist/interaction/controller.js.map +1 -0
  60. package/dist/interaction/index.d.ts +14 -0
  61. package/dist/interaction/index.d.ts.map +1 -0
  62. package/dist/interaction/index.js +13 -0
  63. package/dist/interaction/input.d.ts +26 -0
  64. package/dist/interaction/input.d.ts.map +1 -0
  65. package/dist/interaction/input.js +33 -0
  66. package/dist/interaction/input.js.map +1 -0
  67. package/dist/interaction/intents.d.ts +80 -0
  68. package/dist/interaction/intents.d.ts.map +1 -0
  69. package/dist/interaction/keyboard.d.ts +26 -0
  70. package/dist/interaction/keyboard.d.ts.map +1 -0
  71. package/dist/interaction/keyboard.js +32 -0
  72. package/dist/interaction/keyboard.js.map +1 -0
  73. package/dist/interaction/picking.d.ts +33 -0
  74. package/dist/interaction/picking.d.ts.map +1 -0
  75. package/dist/interaction/picking.js +35 -0
  76. package/dist/interaction/picking.js.map +1 -0
  77. package/dist/interaction/pipeline.d.ts +45 -0
  78. package/dist/interaction/pipeline.d.ts.map +1 -0
  79. package/dist/interaction/pipeline.js +26 -0
  80. package/dist/interaction/pipeline.js.map +1 -0
  81. package/dist/interaction/placement.d.ts +76 -0
  82. package/dist/interaction/placement.d.ts.map +1 -0
  83. package/dist/interaction/placement.js +238 -0
  84. package/dist/interaction/placement.js.map +1 -0
  85. package/dist/interaction/selection-tool.d.ts +29 -0
  86. package/dist/interaction/selection-tool.d.ts.map +1 -0
  87. package/dist/interaction/selection-tool.js +190 -0
  88. package/dist/interaction/selection-tool.js.map +1 -0
  89. package/dist/interaction/steps.d.ts +62 -0
  90. package/dist/interaction/steps.d.ts.map +1 -0
  91. package/dist/interaction/steps.js +126 -0
  92. package/dist/interaction/steps.js.map +1 -0
  93. package/dist/interaction/store.d.ts +10 -0
  94. package/dist/interaction/store.d.ts.map +1 -0
  95. package/dist/interaction/store.js +28 -0
  96. package/dist/interaction/store.js.map +1 -0
  97. package/dist/interaction/tools.d.ts +26 -0
  98. package/dist/interaction/tools.d.ts.map +1 -0
  99. package/dist/interaction/tools.js +41 -0
  100. package/dist/interaction/tools.js.map +1 -0
  101. package/dist/model/assets.d.ts +36 -0
  102. package/dist/model/assets.d.ts.map +1 -0
  103. package/dist/model/assets.js +18 -0
  104. package/dist/model/assets.js.map +1 -0
  105. package/dist/model/committer.d.ts +66 -0
  106. package/dist/model/committer.d.ts.map +1 -0
  107. package/dist/model/committer.js +38 -0
  108. package/dist/model/committer.js.map +1 -0
  109. package/dist/model/core-patches.d.ts +2 -0
  110. package/dist/model/core-patches.d.ts.map +1 -0
  111. package/dist/model/core-patches.js +193 -0
  112. package/dist/model/core-patches.js.map +1 -0
  113. package/dist/model/define-map.d.ts +52 -0
  114. package/dist/model/define-map.d.ts.map +1 -0
  115. package/dist/model/define-map.js +66 -0
  116. package/dist/model/define-map.js.map +1 -0
  117. package/dist/model/definition.d.ts +57 -0
  118. package/dist/model/definition.d.ts.map +1 -0
  119. package/dist/model/definition.js +6 -0
  120. package/dist/model/definition.js.map +1 -0
  121. package/dist/model/errors.d.ts +21 -0
  122. package/dist/model/errors.d.ts.map +1 -0
  123. package/dist/model/errors.js +14 -0
  124. package/dist/model/errors.js.map +1 -0
  125. package/dist/model/history.d.ts +43 -0
  126. package/dist/model/history.d.ts.map +1 -0
  127. package/dist/model/history.js +141 -0
  128. package/dist/model/history.js.map +1 -0
  129. package/dist/model/index.d.ts +14 -0
  130. package/dist/model/index.d.ts.map +1 -0
  131. package/dist/model/index.js +14 -0
  132. package/dist/model/json.d.ts +15 -0
  133. package/dist/model/json.d.ts.map +1 -0
  134. package/dist/model/json.js +35 -0
  135. package/dist/model/json.js.map +1 -0
  136. package/dist/model/model.d.ts +68 -0
  137. package/dist/model/model.d.ts.map +1 -0
  138. package/dist/model/model.js +170 -0
  139. package/dist/model/model.js.map +1 -0
  140. package/dist/model/parse.d.ts +24 -0
  141. package/dist/model/parse.d.ts.map +1 -0
  142. package/dist/model/parse.js +165 -0
  143. package/dist/model/parse.js.map +1 -0
  144. package/dist/model/patches.d.ts +100 -0
  145. package/dist/model/patches.d.ts.map +1 -0
  146. package/dist/model/patches.js +21 -0
  147. package/dist/model/patches.js.map +1 -0
  148. package/dist/model/result.d.ts +22 -0
  149. package/dist/model/result.d.ts.map +1 -0
  150. package/dist/model/result.js +10 -0
  151. package/dist/model/result.js.map +1 -0
  152. package/dist/model/source.d.ts +52 -0
  153. package/dist/model/source.d.ts.map +1 -0
  154. package/dist/model/source.js +75 -0
  155. package/dist/model/source.js.map +1 -0
  156. package/dist/protocol/index.d.ts +92 -0
  157. package/dist/protocol/index.d.ts.map +1 -0
  158. package/dist/protocol/index.js +106 -0
  159. package/dist/protocol/index.js.map +1 -0
  160. package/dist/remote/committer.d.ts +43 -0
  161. package/dist/remote/committer.d.ts.map +1 -0
  162. package/dist/remote/committer.js +206 -0
  163. package/dist/remote/committer.js.map +1 -0
  164. package/dist/remote/index.d.ts +4 -0
  165. package/dist/remote/index.d.ts.map +1 -0
  166. package/dist/remote/index.js +4 -0
  167. package/dist/remote/join.d.ts +30 -0
  168. package/dist/remote/join.d.ts.map +1 -0
  169. package/dist/remote/join.js +36 -0
  170. package/dist/remote/join.js.map +1 -0
  171. package/dist/remote/memory-transport.d.ts +20 -0
  172. package/dist/remote/memory-transport.d.ts.map +1 -0
  173. package/dist/remote/memory-transport.js +48 -0
  174. package/dist/remote/memory-transport.js.map +1 -0
  175. package/dist/renderer/effects.d.ts +52 -0
  176. package/dist/renderer/effects.d.ts.map +1 -0
  177. package/dist/renderer/effects.js +111 -0
  178. package/dist/renderer/effects.js.map +1 -0
  179. package/dist/renderer/feature.d.ts +79 -0
  180. package/dist/renderer/feature.d.ts.map +1 -0
  181. package/dist/renderer/feature.js +50 -0
  182. package/dist/renderer/feature.js.map +1 -0
  183. package/dist/renderer/geometry.d.ts +59 -0
  184. package/dist/renderer/geometry.d.ts.map +1 -0
  185. package/dist/renderer/geometry.js +114 -0
  186. package/dist/renderer/geometry.js.map +1 -0
  187. package/dist/renderer/index.d.ts +7 -0
  188. package/dist/renderer/index.d.ts.map +1 -0
  189. package/dist/renderer/index.js +7 -0
  190. package/dist/renderer/scene.d.ts +77 -0
  191. package/dist/renderer/scene.d.ts.map +1 -0
  192. package/dist/renderer/scene.js +125 -0
  193. package/dist/renderer/scene.js.map +1 -0
  194. package/dist/renderer/textures.d.ts +20 -0
  195. package/dist/renderer/textures.d.ts.map +1 -0
  196. package/dist/renderer/textures.js +61 -0
  197. package/dist/renderer/textures.js.map +1 -0
  198. package/dist/renderer/view.d.ts +139 -0
  199. package/dist/renderer/view.d.ts.map +1 -0
  200. package/dist/renderer/view.js +349 -0
  201. package/dist/renderer/view.js.map +1 -0
  202. package/dist/rpgjs/shared.d.ts +41 -0
  203. package/dist/rpgjs/shared.d.ts.map +1 -0
  204. package/dist/rpgjs/shared.js +77 -0
  205. package/dist/rpgjs/shared.js.map +1 -0
  206. package/dist/server/index.d.ts +64 -0
  207. package/dist/server/index.d.ts.map +1 -0
  208. package/dist/server/index.js +205 -0
  209. package/dist/server/index.js.map +1 -0
  210. package/dist/server/storage.d.ts +16 -0
  211. package/dist/server/storage.d.ts.map +1 -0
  212. package/dist/server/storage.js +52 -0
  213. package/dist/server/storage.js.map +1 -0
  214. package/dist/skins/default.d.ts +27 -0
  215. package/dist/skins/default.d.ts.map +1 -0
  216. package/dist/skins/default.js +144 -0
  217. package/dist/skins/default.js.map +1 -0
  218. package/dist/skins/frames.d.ts +19 -0
  219. package/dist/skins/frames.d.ts.map +1 -0
  220. package/dist/skins/frames.js +49 -0
  221. package/dist/skins/frames.js.map +1 -0
  222. package/dist/skins/game.d.ts +21 -0
  223. package/dist/skins/game.d.ts.map +1 -0
  224. package/dist/skins/game.js +21 -0
  225. package/dist/skins/game.js.map +1 -0
  226. package/dist/skins/index.d.ts +5 -0
  227. package/dist/skins/index.d.ts.map +1 -0
  228. package/dist/skins/index.js +5 -0
  229. package/dist/skins/types.d.ts +46 -0
  230. package/dist/skins/types.d.ts.map +1 -0
  231. package/dist/skins/types.js +12 -0
  232. package/dist/skins/types.js.map +1 -0
  233. package/dist/terrain/data.d.ts +10 -0
  234. package/dist/terrain/data.d.ts.map +1 -0
  235. package/dist/terrain/data.js +83 -0
  236. package/dist/terrain/data.js.map +1 -0
  237. package/dist/terrain/define.d.ts +72 -0
  238. package/dist/terrain/define.d.ts.map +1 -0
  239. package/dist/terrain/define.js +91 -0
  240. package/dist/terrain/define.js.map +1 -0
  241. package/dist/terrain/editing.d.ts +54 -0
  242. package/dist/terrain/editing.d.ts.map +1 -0
  243. package/dist/terrain/editing.js +169 -0
  244. package/dist/terrain/editing.js.map +1 -0
  245. package/dist/terrain/index.d.ts +10 -0
  246. package/dist/terrain/index.d.ts.map +1 -0
  247. package/dist/terrain/index.js +10 -0
  248. package/dist/terrain/kernel.d.ts +57 -0
  249. package/dist/terrain/kernel.d.ts.map +1 -0
  250. package/dist/terrain/kernel.js +198 -0
  251. package/dist/terrain/kernel.js.map +1 -0
  252. package/dist/terrain/mask.d.ts +37 -0
  253. package/dist/terrain/mask.d.ts.map +1 -0
  254. package/dist/terrain/mask.js +244 -0
  255. package/dist/terrain/mask.js.map +1 -0
  256. package/dist/terrain/patches.d.ts +32 -0
  257. package/dist/terrain/patches.d.ts.map +1 -0
  258. package/dist/terrain/patches.js +135 -0
  259. package/dist/terrain/patches.js.map +1 -0
  260. package/dist/terrain/rle.d.ts +4 -0
  261. package/dist/terrain/rle.d.ts.map +1 -0
  262. package/dist/terrain/rle.js +68 -0
  263. package/dist/terrain/rle.js.map +1 -0
  264. package/dist/terrain/scheduler.d.ts +46 -0
  265. package/dist/terrain/scheduler.d.ts.map +1 -0
  266. package/dist/terrain/scheduler.js +148 -0
  267. package/dist/terrain/scheduler.js.map +1 -0
  268. package/dist/terrain/types.d.ts +110 -0
  269. package/dist/terrain/types.d.ts.map +1 -0
  270. package/dist/terrain/types.js +11 -0
  271. package/dist/terrain/types.js.map +1 -0
  272. package/dist/terrain-render/backend.d.ts +66 -0
  273. package/dist/terrain-render/backend.d.ts.map +1 -0
  274. package/dist/terrain-render/backend.js +96 -0
  275. package/dist/terrain-render/backend.js.map +1 -0
  276. package/dist/terrain-render/host.d.ts +21 -0
  277. package/dist/terrain-render/host.d.ts.map +1 -0
  278. package/dist/terrain-render/host.js +62 -0
  279. package/dist/terrain-render/host.js.map +1 -0
  280. package/dist/terrain-render/index.d.ts +3 -0
  281. package/dist/terrain-render/index.d.ts.map +1 -0
  282. package/dist/terrain-render/index.js +3 -0
  283. package/dist/terrain-render/worker.d.ts +2 -0
  284. package/dist/terrain-render/worker.d.ts.map +1 -0
  285. package/dist/terrain-render/worker.js +34 -0
  286. package/dist/terrain-render/worker.js.map +1 -0
  287. package/package.json +124 -3
package/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2026 by Samuel Ronce
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,342 @@
1
- # Temporary Holding Version
1
+ # `@rpgjs/map-builder`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **Experimental, private.** The API changes until the Studio integration
4
+ > validates it. See [`MAP_BUILDER.md`](../../MAP_BUILDER.md) for the design.
5
+
6
+ A modifiable map: one model, changed only by patches, usable by a game, an
7
+ editor or both.
8
+
9
+ | Entry | What it gives | Needs PixiJS |
10
+ | --- | --- | --- |
11
+ | `@rpgjs/map-builder/model` | The model, patches, history, sources. Runs in plain Node, without a DOM. | no |
12
+ | `@rpgjs/map-builder/renderer` | `createMapView`: draws a model with PixiJS, updates it patch by patch. | yes (peer dependency) |
13
+ | `@rpgjs/map-builder/features` | `elements()`, `events()`, `selection()`, `placement()`, `history()`. | yes (through the skins) |
14
+ | `@rpgjs/map-builder/interaction` | The headless layer: state, tools, intents, pipelines, catalog. Runs on a server, to validate with the same rules. | no |
15
+ | `@rpgjs/map-builder/skins` | How the interaction looks: `defaultSkin`, `gameSkin`, and the contract to write your own. | yes |
16
+ | `@rpgjs/map-builder/protocol` | The messages between a client and the authority of a map, validated. | no |
17
+ | `@rpgjs/map-builder/authority` | The authority over a map: decides, tells everybody, catches clients up, persists. Runs on a server. | no |
18
+ | `@rpgjs/map-builder/remote` | The client side: a model that asks the authority and follows everybody's changes. | no |
19
+ | `@rpgjs/map-builder/server` | RPGJS: one authority per map, colliders, events. | no (needs `@rpgjs/server`) |
20
+ | `@rpgjs/map-builder/client` | RPGJS: joins the map, view, prediction colliders. | yes (needs `@rpgjs/client`) |
21
+
22
+ ```ts
23
+ import { createMapModel, defineAssets, defineMap, el } from "@rpgjs/map-builder/model";
24
+
25
+ const assets = defineAssets({ "tree-oak": { src: "/img/tree-oak.png" } });
26
+
27
+ // A map written in code. References to assets are checked by the compiler.
28
+ const forest = defineMap(assets, {
29
+ id: "forest",
30
+ size: [2560, 1920], // pixels
31
+ elements: [el("tree-oak", { id: "t1", x: 128, y: 96, data: { hits: 5 } })],
32
+ });
33
+
34
+ const model = createMapModel(forest);
35
+
36
+ model.subscribe(({ patches }) => console.log("changed", patches));
37
+
38
+ model.apply({ type: "element.move", id: "t1", x: 160, y: 96 });
39
+
40
+ // A batch is atomic and gets a single revision.
41
+ model.apply([
42
+ { type: "element.remove", id: "t1" },
43
+ { type: "event.add", event: { id: "wood-1", kind: "loot", x: 160, y: 96 } },
44
+ ]);
45
+
46
+ model.history.undo(); // restores the tree and removes the loot, in one step
47
+ ```
48
+
49
+ ## Drawing a map
50
+
51
+ ```ts
52
+ import { createMapView } from "@rpgjs/map-builder/renderer";
53
+ import { elements, events } from "@rpgjs/map-builder/features";
54
+
55
+ const view = createMapView({
56
+ model,
57
+ assets,
58
+ features: [elements(), events()], // imported objects, so unused ones are tree-shaken
59
+ });
60
+
61
+ await view.mount(document.getElementById("game")!); // creates a PixiJS application…
62
+ // …or host `view.root` in any PixiJS stage you already have:
63
+ app.stage.addChild(view.root);
64
+
65
+ model.apply({ type: "element.move", id: "t1", x: 160, y: 96 }); // only t1 is updated
66
+ ```
67
+
68
+ The view follows the model whatever its committer: it listens to the changes
69
+ that were applied, and describes again only the elements a patch touched.
70
+
71
+ - **Depth.** Elements are drawn back to front by a sort key: the bottom of the
72
+ hitbox (or of the drawing without one) plus `depth.z`, as the Studio renderer
73
+ does. `depth.anchorY` replaces the bottom with an explicit offset.
74
+ - **Assets.** `AssetDescriptor` has an optional `frame` (an asset cut out of a
75
+ tileset), a `size`, and a `hitbox` in source pixels. An asset that declares
76
+ neither `frame` nor `size` is measured once its image has loaded.
77
+ - **Culling.** `view.setViewport(rect)` hides what is outside the camera, with a
78
+ margin (64 px by default). `null` shows everything.
79
+ - **Hooks.** `resolveElementSprite` changes how an element is drawn (Studio uses
80
+ it for its drawing rules), `onBeforeRenderElement` adjusts the result,
81
+ `onMissingAsset` reports unknown assets.
82
+ - **Effects.** Short visual changes that never touch the model, so they are not
83
+ saved, undoable nor replayed to late joiners:
84
+
85
+ ```ts
86
+ const handle = view.effects.play("t1", "shake", { intensity: 4 });
87
+ await handle.finished; // "completed" | "cancelled" | "target-removed" | …
88
+ ```
89
+
90
+ `shake` and `blink` are built in; add yours with `effects: { lift }` or
91
+ `view.effects.register`. Time only moves on `view.tick(deltaMs)`, which `mount`
92
+ calls every frame, so effects are deterministic.
93
+
94
+ ## Editing, and playing, on the same map
95
+
96
+ The same view serves a game and an editor. What changes is the list of features,
97
+ the active modes and the rules.
98
+
99
+ ```ts
100
+ const catalog = defineCatalog([
101
+ { id: "oak", sprite: "tree-oak", pipeline: [/* rules of this item */] },
102
+ ]);
103
+
104
+ const view = createMapView({
105
+ model,
106
+ assets,
107
+ features: [
108
+ elements({ pipeline: { move: [snapToGrid(16)] } }), // rules for every move, whoever asks
109
+ events(),
110
+ selection({ drag: { live: true }, remove: true }),
111
+ placement({ catalog, pipeline: [snapToGrid(32), withinMap(), requireFree()] }),
112
+ history(),
113
+ ],
114
+ modes: ["edit"],
115
+ });
116
+
117
+ view.placement.start("oak"); // a ghost follows the pointer; typed on the catalog ids
118
+ view.selection.current; // what is selected
119
+ view.history.undo();
120
+ view.submit({ type: "element.move", id: "t1", x: 160, y: 96 }); // an intent, not a patch
121
+ ```
122
+
123
+ Features are imported objects, and each one that has an API exposes it on the view
124
+ under its id: `view.placement`, `view.selection`, `view.history`. The view is typed
125
+ by the list you give: without `placement()`, `view.placement` does not exist.
126
+
127
+ ### Intents, pipelines, rejections
128
+
129
+ A user action is an **intent**. It goes through a **pipeline** of steps, then
130
+ becomes patches, which the committer applies. A step may modify the intent, refuse it
131
+ or pass it on, in the style of Koa:
132
+
133
+ ```ts
134
+ const rules: PipelineStep<"element.place">[] = [
135
+ snapToGrid(32),
136
+ maxCount("tree-oak", 5),
137
+ (intent, ctx, next) => (ctx.actor.inventory.wood >= 2 ? next() : ctx.reject("not-enough-wood")),
138
+ ];
139
+ ```
140
+
141
+ Steps are synchronous and pure, so **the same pipeline runs on the client**, to
142
+ preview with the ghost, **and on a server**, to validate. A refusal is a value:
143
+ `view.submit(...)` returns `{ ok: false, rejection: { code: "occupied", … } }` and
144
+ `interaction.state.get().lastRejection` keeps it, so the UI can say why.
145
+
146
+ Provided steps: `snapToGrid`, `withinMap`, `requireFree`, `maxCount`, `minDistance`.
147
+
148
+ ### Modes
149
+
150
+ Tools belong to modes (`play`, `edit`, `inspect`). Several modes can be active at
151
+ once, and a tool can be gated on a modifier key, which is how a player plays while
152
+ Alt turns the same click into editing:
153
+
154
+ ```ts
155
+ view.interaction.addTool(playTool({ types: ["event"], onInteract: (target) => talkTo(target) }));
156
+ view.interaction.setModes(["play", "edit"]);
157
+ ```
158
+
159
+ The interaction layer is **headless**: it publishes `interaction.state` (hovered,
160
+ selection, drag, rubber band, ghost, last rejection) and draws nothing. Skins read
161
+ that state:
162
+
163
+ ```ts
164
+ selection({ skin: { selection: myGlow } }); // replace one part, keep the others
165
+ placement({ catalog, skin: gameSkin({ ghost: { valid: 0x66ff66, invalid: 0xff5555 }, pulse: true }) });
166
+ ```
167
+
168
+ Keyboard (Escape cancels, Delete removes) is opt-in: `view.mount(host, { keyboard: true })`
169
+ or `bindKeyboard(view.interaction, window)`.
170
+
171
+ ### Adding an intent
172
+
173
+ ```ts
174
+ declare module "@rpgjs/map-builder/interaction" {
175
+ interface IntentRegistry {
176
+ "tilling.till": { readonly x: number; readonly y: number };
177
+ }
178
+ }
179
+ ```
180
+
181
+ ## Multiplayer
182
+
183
+ One **authority** holds the truth of a map. Clients **ask** with intents, the authority
184
+ decides with the same pipelines the clients use to preview, and **tells everybody** what
185
+ became true, in the same order. None of this depends on a framework: connect it to
186
+ whatever carries messages. (`@rpgjs/map-builder/server` and `/client` do it for RPGJS.)
187
+
188
+ ```ts
189
+ // server
190
+ const authority = createMapAuthority({
191
+ definition,
192
+ assets, // sizes and hitboxes come from here
193
+ pipeline: { move: [ownerOnly({ actor: (who) => who.id }), protectedZone([square])] },
194
+ placement: { catalog, pipeline: [snapToGrid(32), requireFree(), rateLimit({ max: 5, windowMs: 1000 })], stamp: (who) => ({ owner: who.id }) },
195
+ persist: { store, id: "village" }, // snapshots and a log; restoreMapAuthority brings it back
196
+ });
197
+ const connection = authority.connect({ id, actor: player, send: (message) => socket.send(message) });
198
+ socket.onMessage((raw) => connection.receive(raw)); // anything invalid is ignored
199
+
200
+ authority.apply({ type: "element.remove", id: "tree-3" }); // the game itself: a lever, a quest
201
+ authority.effect("tree-3", "shake"); // shown to everybody, never kept
202
+
203
+ // client
204
+ const remote = await joinRemoteMap({ transport }); // { send(message), onMessage(listener) }
205
+ const view = createMapView({ model: remote.model, assets, features: [elements(), placement({ catalog })] });
206
+ bindRemoteEffects(remote, view.effects);
207
+ ```
208
+
209
+ - **The client never decides.** Its model changes when the authority says so. `submit`
210
+ and `placement` return a promise that resolves with the decision; a refusal carries the
211
+ reason (`"occupied"`, `"not-owner"`, `"rate-limit"`…). Patches are only accepted from
212
+ clients the authority trusts (`trustPatches`), which is what lets an editor undo.
213
+ - **The authority gives the ids.** A client places something without an id and reads the one
214
+ it was given, so two players can never choose the same.
215
+ - **Late clients catch up.** The authority keeps a log of recent changes: a client that is a
216
+ little behind gets just those, one that is too far behind gets the map. A hole in the
217
+ revisions makes a client ask again by itself.
218
+ - **Persistence.** A `MapStore` keeps a snapshot, written every so many revisions or a
219
+ moment after the last change, and, if it can `append`, every change in between.
220
+ `restoreMapAuthority` loads the snapshot and replays the log, and refuses a log with a hole.
221
+ - **Rules for a shared world.** `ownerOnly`, `rateLimit` (a preview does not count),
222
+ `protectedZone`, next to `snapToGrid`, `requireFree`, `maxCount`… The same steps run on
223
+ the client to preview and on the authority to decide.
224
+ - `connectInMemory(authority)` links a client and an authority with no network: for tests,
225
+ demos, and single-player games that still want the rules.
226
+
227
+ ## In an RPGJS game
228
+
229
+ ```ts
230
+ // server.ts
231
+ import { createServer } from "@rpgjs/server";
232
+ import { provideMapBuilder, getMapAuthority } from "@rpgjs/map-builder/server";
233
+
234
+ export default createServer({
235
+ providers: [
236
+ provideMapBuilder({
237
+ assets, // a server has no image to measure: declare `size` or `frame`
238
+ maps: { forest }, // by RPGJS map id; a function or a MapSource work too
239
+ authority: { placement: { catalog, pipeline: [snapToGrid(32), requireFree()] } },
240
+ persist: (map) => ({ store: roomStorageMapStore(map) }), // optional
241
+ }),
242
+ ],
243
+ });
244
+
245
+ // anywhere in game code: a lever
246
+ getMapAuthority(player.getCurrentMap())?.apply([{ type: "element.remove", id: "gate" }]);
247
+
248
+ // client.ts
249
+ import { startGame } from "@rpgjs/client";
250
+ import { provideMapBuilder, getMapBuilder } from "@rpgjs/map-builder/client";
251
+
252
+ startGame({
253
+ providers: [
254
+ provideMapBuilder({
255
+ assets,
256
+ features: [elements(), selection(), placement({ catalog })],
257
+ onJoin: (handle) => onKey("b", () => handle.view.placement.start("oak")),
258
+ }),
259
+ ],
260
+ });
261
+ ```
262
+
263
+ - **One authority per map**, created when the map loads and restored from the store if there is
264
+ one. A player is connected to the map they are on and to no other; what they send is checked
265
+ against the map it is about.
266
+ - **Colliders**: each element with a hitbox gets a static collider in the physics of the server
267
+ and of the client (prediction), one namespace per element, so building only touches the
268
+ collider that changed. They are put back whenever the physics is rebuilt.
269
+ - **Events** of the map become RPGJS dynamic events (`builder:<id>`), drawn with their `graphic`.
270
+ Give them behaviour with `events: { define: (event) => ({ hooks: { onAction } }) }`. They are
271
+ recreated when RPGJS wipes them on a map update. The client does not use `events()` for them,
272
+ since RPGJS already draws events.
273
+ - **Drawing**: pass the view to the event layer of the map, so elements and characters are sorted
274
+ in depth together: `<EventLayerComponent pixiChildren={getMapBuilder(engine).pixiChildren} />`.
275
+ `handle.attachTo(container)` adds it anywhere else, without that sorting.
276
+ - Standalone and MMORPG work the same: only `socket.emit`, `player.on` and `map.broadcast` are used.
277
+
278
+ ### In a CanvasEngine scene
279
+
280
+ The view is a PixiJS container, which is what CanvasEngine renders to. Host it
281
+ the way other raw PixiJS objects are hosted:
282
+
283
+ ```ce
284
+ <Container />
285
+
286
+ <script>
287
+ import { mount, tick } from "canvasengine";
288
+ import { attachMapView } from "@rpgjs/map-builder/renderer";
289
+
290
+ mount((element) => attachMapView(element.componentInstance, view));
291
+ tick(({ deltaTime }) => view.tick(deltaTime));
292
+ </script>
293
+ ```
294
+
295
+ ## What is in it
296
+
297
+ | | |
298
+ | --- | --- |
299
+ | `defineAssets`, `defineMap`, `el`, `event` | Describe a map in code. The result is a plain, serializable `MapDefinition`. |
300
+ | `parseMapDefinition` | Validates, migrates and copies raw JSON. Unknown fields are preserved. |
301
+ | `MapSource`: `codeMapSource`, `jsonMapSource`, `httpMapSource` | Where maps come from. `loadRegion` streams large maps. |
302
+ | `AssetResolver` | Turns `"tree-oak"` into an image descriptor. |
303
+ | `createMapModel` | State, revisions, patches, batches, history, `subscribe`. |
304
+ | Patches | `element.add/remove/move/update/data`, `event.add/remove/move`, `extension.set`. Serializable and invertible. |
305
+ | `localCommitter` | Applies changes immediately, and saves them through `persist`. |
306
+ | `createMapView`, `elements()`, `events()` | Draw the model with PixiJS, incrementally. |
307
+ | `defineMapFeature` | Declares a feature with an id, its dependencies and what it draws. Built-in and third-party features use the same contract. |
308
+
309
+ ## Rejections are values
310
+
311
+ ```ts
312
+ const result = model.apply({ type: "element.move", id: "nope", x: 1, y: 1 });
313
+ if (!result.ok) result.rejection; // { code: "not-found", message, patchIndex: 0 }
314
+ ```
315
+
316
+ ## Adding a patch type
317
+
318
+ A feature extends the registry, so its patches are checked everywhere:
319
+
320
+ ```ts
321
+ declare module "@rpgjs/map-builder/model" {
322
+ interface PatchRegistry {
323
+ "tilling.till": { readonly x: number; readonly y: number };
324
+ }
325
+ }
326
+
327
+ createMapModel(definition, {
328
+ patchHandlers: [
329
+ definePatchHandlers({
330
+ "tilling.till": { apply: (state, patch) => patchOk(/* new state */ state, /* inverse patches */ []) },
331
+ }),
332
+ ],
333
+ });
334
+ ```
335
+
336
+ ## Scripts
337
+
338
+ ```bash
339
+ pnpm --filter @rpgjs/map-builder test # unit tests, in Node
340
+ pnpm --filter @rpgjs/map-builder test:types # strict type check, including the type tests
341
+ pnpm --filter @rpgjs/map-builder build
342
+ ```
@@ -0,0 +1,118 @@
1
+ import { AssetResolver } from '../model/assets';
2
+ import { MapDefinition } from '../model/definition';
3
+ import { MapChange, MapModel } from '../model/model';
4
+ import { PatchHandlers, MapPatch } from '../model/patches';
5
+ import { ApplyFailure, ApplyResult } from '../model/result';
6
+ import { Catalog } from '../interaction/catalog';
7
+ import { InteractionController } from '../interaction/controller';
8
+ import { MapIntent } from '../interaction/intents';
9
+ import { PipelineStep } from '../interaction/pipeline';
10
+ import { PlacementIntentOptions } from '../interaction/placement';
11
+ import { EffectTargetMessage, ServerMessage } from '../protocol';
12
+ import { MapViewHooks } from '../renderer/scene';
13
+ import { MapStore, PatchEntry } from './store';
14
+ /** A client connected to the authority. The authority does not care how: `send` is all it needs. */
15
+ export interface AuthoritySession {
16
+ readonly id: string;
17
+ /** Who is acting: whatever the game decides, handed to the steps of the pipelines. */
18
+ readonly actor: unknown;
19
+ send(message: ServerMessage): void;
20
+ }
21
+ export interface AuthorityConnection {
22
+ readonly session: AuthoritySession;
23
+ /** Gives the authority what the client sent. Anything that is not a valid message is ignored. */
24
+ receive(raw: unknown): void;
25
+ disconnect(): void;
26
+ }
27
+ export interface PersistOptions {
28
+ readonly store: MapStore;
29
+ /** Key of the map in the store. */
30
+ readonly id: string;
31
+ /** Write a snapshot every this many revisions. Defaults to 50. */
32
+ readonly snapshotEvery?: number;
33
+ /** Write a snapshot this long after the last change. Defaults to 1000. */
34
+ readonly saveDelayMs?: number;
35
+ /** Replaces `setTimeout`, so tests control time. Returns the function that cancels it. */
36
+ readonly schedule?: (run: () => void, delayMs: number) => () => void;
37
+ /**
38
+ * The store already holds a snapshot of the state the authority starts from, so it does
39
+ * not write one first. `restoreMapAuthority` sets it. Otherwise the authority writes the
40
+ * starting snapshot itself, so that the changes it appends have something to follow.
41
+ */
42
+ readonly baseSaved?: boolean;
43
+ }
44
+ export interface MapAuthorityOptions<C extends Catalog<string> = Catalog<string>> {
45
+ readonly definition: MapDefinition;
46
+ /** Revision the map starts at. Defaults to 0. */
47
+ readonly revision?: number;
48
+ /**
49
+ * What the map is made of. Sizes and hitboxes come from here, so assets must declare
50
+ * a `size` or a `frame`: a server has no image to measure.
51
+ */
52
+ readonly assets: AssetResolver;
53
+ readonly hooks?: MapViewHooks;
54
+ /** Rules for every move and removal, whoever asks. The same steps the clients use to preview. */
55
+ readonly pipeline?: {
56
+ readonly move?: readonly PipelineStep<"element.move">[];
57
+ readonly remove?: readonly PipelineStep<"element.remove">[];
58
+ };
59
+ /** What can be placed, and the rules for it. */
60
+ readonly placement?: PlacementIntentOptions<C>;
61
+ /** Handlers of the patch types features add (`terrainPatchHandlers`): the authority must know them to apply them. */
62
+ readonly patchHandlers?: readonly PatchHandlers[];
63
+ /** Registers more intents and pipelines, e.g. those of a custom feature. */
64
+ readonly setup?: (controller: InteractionController) => void;
65
+ /**
66
+ * Whether this actor may send patches as they are (an editor, an undo). Never by default:
67
+ * a client asks with intents, and the authority decides.
68
+ */
69
+ readonly trustPatches?: (actor: unknown) => boolean;
70
+ /** Who made a change, in the words other players see. */
71
+ readonly author?: (actor: unknown) => string | undefined;
72
+ /** Changes kept to catch late clients up without sending the whole map. Defaults to 512. */
73
+ readonly log?: number;
74
+ readonly persist?: PersistOptions;
75
+ /** Called after every change, whoever made it: the place to keep a physics world in step. */
76
+ readonly onChange?: (change: MapChange) => void;
77
+ readonly onError?: (error: unknown) => void;
78
+ /** Changes to replay on top of `definition`, in order: what a store gives back after a restart. */
79
+ readonly replay?: readonly PatchEntry[];
80
+ }
81
+ export interface MapAuthority {
82
+ /** The truth. Never change it directly: use `apply`, so everybody hears about it. */
83
+ readonly model: MapModel<ApplyResult>;
84
+ readonly revision: number;
85
+ readonly interaction: InteractionController;
86
+ /** How many clients are connected. */
87
+ readonly connections: number;
88
+ connect(session: AuthoritySession): AuthorityConnection;
89
+ /**
90
+ * A change the game itself makes (a lever, a quest). It is trusted: no pipeline. Everybody
91
+ * connected hears about it.
92
+ */
93
+ apply(patches: MapPatch | readonly MapPatch[]): ApplyResult;
94
+ /** Runs intents through the pipelines on behalf of an actor, e.g. a non-player character. */
95
+ submit(intents: MapIntent | readonly MapIntent[], actor?: unknown): ApplyResult | ApplyFailure;
96
+ /** Shows a short effect to everybody connected. It is not kept. */
97
+ effect(target: EffectTargetMessage, name: string, params?: Readonly<Record<string, unknown>>): void;
98
+ /** The map as it is now, as a message. */
99
+ snapshot(): ServerMessage;
100
+ /** Writes what is pending to the store. */
101
+ flush(): Promise<void>;
102
+ /** Flushes, then stops. */
103
+ destroy(): Promise<void>;
104
+ }
105
+ /**
106
+ * The authority over one map: it holds the truth, decides what clients may do, tells
107
+ * everybody what became true, catches late clients up and keeps the map between
108
+ * restarts. It knows nothing of any framework: connect it to whatever carries messages.
109
+ */
110
+ export declare function createMapAuthority<C extends Catalog<string> = Catalog<string>>(options: MapAuthorityOptions<C>): MapAuthority;
111
+ /**
112
+ * Brings a map back from a store, or starts it from `definition` when the store has
113
+ * nothing yet. Changes written after the last snapshot are replayed.
114
+ */
115
+ export declare function restoreMapAuthority<C extends Catalog<string> = Catalog<string>>(options: MapAuthorityOptions<C> & {
116
+ readonly persist: PersistOptions;
117
+ }): Promise<MapAuthority>;
118
+ //# sourceMappingURL=authority.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authority.d.ts","sourceRoot":"","sources":["../../src/authority/authority.ts"],"names":[],"mappings":"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"}