@jgengine/core 0.6.0 → 0.8.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 (330) hide show
  1. package/CHANGELOG.md +214 -2
  2. package/README.md +2 -0
  3. package/dist/ai/crowd.d.ts +36 -0
  4. package/dist/ai/crowd.js +213 -0
  5. package/dist/ai/jobBoard.d.ts +49 -0
  6. package/dist/ai/jobBoard.js +155 -0
  7. package/dist/ai/spawnDirector.d.ts +61 -0
  8. package/dist/ai/spawnDirector.js +162 -0
  9. package/dist/ai/threat.d.ts +25 -0
  10. package/dist/ai/threat.js +67 -0
  11. package/dist/anim/easing.d.ts +14 -0
  12. package/dist/anim/easing.js +55 -0
  13. package/dist/audio/audioFalloff.d.ts +38 -0
  14. package/dist/audio/audioFalloff.js +32 -0
  15. package/dist/behaviour/behaviour.d.ts +136 -0
  16. package/dist/behaviour/behaviour.js +388 -0
  17. package/dist/board/laneBoard.d.ts +63 -0
  18. package/dist/board/laneBoard.js +114 -0
  19. package/dist/board/timelineBoard.d.ts +36 -0
  20. package/dist/board/timelineBoard.js +70 -0
  21. package/dist/cards/cardPile.d.ts +64 -0
  22. package/dist/cards/cardPile.js +175 -0
  23. package/dist/cards/modifierPipeline.d.ts +33 -0
  24. package/dist/cards/modifierPipeline.js +48 -0
  25. package/dist/combat/abilityKit.d.ts +47 -0
  26. package/dist/combat/abilityKit.js +139 -0
  27. package/dist/combat/animationState.d.ts +47 -0
  28. package/dist/combat/animationState.js +101 -0
  29. package/dist/combat/attackTags.d.ts +14 -0
  30. package/dist/combat/attackTags.js +26 -0
  31. package/dist/combat/breakMeters.d.ts +32 -0
  32. package/dist/combat/breakMeters.js +51 -0
  33. package/dist/combat/comboString.d.ts +35 -0
  34. package/dist/combat/comboString.js +59 -0
  35. package/dist/combat/death.d.ts +8 -0
  36. package/dist/combat/death.js +5 -2
  37. package/dist/combat/defensiveWindow.d.ts +34 -0
  38. package/dist/combat/defensiveWindow.js +66 -0
  39. package/dist/combat/downed.d.ts +35 -0
  40. package/dist/combat/downed.js +104 -0
  41. package/dist/combat/effects.d.ts +12 -1
  42. package/dist/combat/effects.js +31 -19
  43. package/dist/combat/hitReaction.d.ts +23 -0
  44. package/dist/combat/hitReaction.js +20 -0
  45. package/dist/combat/projectiles.d.ts +42 -5
  46. package/dist/combat/projectiles.js +98 -12
  47. package/dist/combat/resistance.d.ts +13 -0
  48. package/dist/combat/resistance.js +46 -0
  49. package/dist/combat/telegraph.d.ts +31 -0
  50. package/dist/combat/telegraph.js +51 -0
  51. package/dist/commands/commandRegistry.d.ts +1 -1
  52. package/dist/commands/commandRegistry.js +2 -1
  53. package/dist/crafting/crop.d.ts +59 -0
  54. package/dist/crafting/crop.js +181 -0
  55. package/dist/crafting/production.d.ts +64 -0
  56. package/dist/crafting/production.js +130 -0
  57. package/dist/crafting/recipe.d.ts +62 -0
  58. package/dist/crafting/recipe.js +101 -0
  59. package/dist/data/dataSource.d.ts +26 -0
  60. package/dist/data/dataSource.js +93 -0
  61. package/dist/data/devProxy.d.ts +11 -0
  62. package/dist/data/devProxy.js +38 -0
  63. package/dist/data/fetchJson.d.ts +20 -0
  64. package/dist/data/fetchJson.js +43 -0
  65. package/dist/data/jsonDataSource.d.ts +4 -0
  66. package/dist/data/jsonDataSource.js +6 -0
  67. package/dist/devtools/devtools.d.ts +157 -0
  68. package/dist/devtools/devtools.js +484 -0
  69. package/dist/devtools/transformTunables.d.ts +14 -0
  70. package/dist/devtools/transformTunables.js +38 -0
  71. package/dist/economy/sharedWallet.d.ts +28 -0
  72. package/dist/economy/sharedWallet.js +62 -0
  73. package/dist/economy/techTree.d.ts +40 -0
  74. package/dist/economy/techTree.js +66 -0
  75. package/dist/game/chat.d.ts +66 -0
  76. package/dist/game/chat.js +165 -0
  77. package/dist/game/cosmetics.d.ts +25 -0
  78. package/dist/game/cosmetics.js +51 -0
  79. package/dist/game/defineGame.d.ts +10 -2
  80. package/dist/game/defineGame.js +2 -1
  81. package/dist/game/events.d.ts +87 -0
  82. package/dist/game/levelSequence.d.ts +44 -0
  83. package/dist/game/levelSequence.js +78 -0
  84. package/dist/game/lootFilter.d.ts +33 -0
  85. package/dist/game/lootFilter.js +48 -0
  86. package/dist/game/ping.d.ts +67 -0
  87. package/dist/game/ping.js +86 -0
  88. package/dist/game/playableGame.d.ts +516 -8
  89. package/dist/game/playableGame.js +6 -1
  90. package/dist/game/progression.d.ts +1 -0
  91. package/dist/game/progression.js +23 -2
  92. package/dist/game/race.d.ts +100 -0
  93. package/dist/game/race.js +236 -0
  94. package/dist/game/runDraft.d.ts +34 -0
  95. package/dist/game/runDraft.js +97 -0
  96. package/dist/game/social.d.ts +69 -0
  97. package/dist/game/social.js +138 -0
  98. package/dist/game/spawnPoints.d.ts +19 -0
  99. package/dist/game/spawnPoints.js +23 -0
  100. package/dist/game/worldItem.d.ts +95 -0
  101. package/dist/game/worldItem.js +138 -0
  102. package/dist/input/actionBindings.d.ts +10 -0
  103. package/dist/input/actionBindings.js +14 -0
  104. package/dist/input/axisInput.d.ts +43 -0
  105. package/dist/input/axisInput.js +70 -0
  106. package/dist/input/gestureSurface.d.ts +31 -0
  107. package/dist/input/gestureSurface.js +120 -0
  108. package/dist/input/pointer.d.ts +69 -0
  109. package/dist/input/pointer.js +65 -0
  110. package/dist/input/touchScheme.d.ts +83 -0
  111. package/dist/input/touchScheme.js +115 -0
  112. package/dist/interaction/contextMenu.d.ts +30 -0
  113. package/dist/interaction/contextMenu.js +22 -0
  114. package/dist/interaction/qte.d.ts +20 -0
  115. package/dist/interaction/qte.js +24 -0
  116. package/dist/interaction/skillCheck.d.ts +20 -0
  117. package/dist/interaction/skillCheck.js +26 -0
  118. package/dist/inventory/shapedGrid.d.ts +56 -0
  119. package/dist/inventory/shapedGrid.js +176 -0
  120. package/dist/inventory/storageTier.d.ts +52 -0
  121. package/dist/inventory/storageTier.js +77 -0
  122. package/dist/item/affix.d.ts +54 -0
  123. package/dist/item/affix.js +115 -0
  124. package/dist/item/durability.d.ts +51 -0
  125. package/dist/item/durability.js +78 -0
  126. package/dist/item/modularItem.d.ts +46 -0
  127. package/dist/item/modularItem.js +75 -0
  128. package/dist/meta/changelog.d.ts +1 -1
  129. package/dist/meta/changelog.js +71 -1
  130. package/dist/movement/dash.d.ts +37 -0
  131. package/dist/movement/dash.js +65 -0
  132. package/dist/movement/movementModel.d.ts +36 -3
  133. package/dist/movement/movementModel.js +81 -5
  134. package/dist/movement/voxelController.d.ts +61 -0
  135. package/dist/movement/voxelController.js +229 -0
  136. package/dist/multiplayer/chatContract.d.ts +42 -0
  137. package/dist/multiplayer/chatContract.js +57 -0
  138. package/dist/multiplayer/combatSnapshot.d.ts +45 -0
  139. package/dist/multiplayer/combatSnapshot.js +89 -0
  140. package/dist/multiplayer/identity.d.ts +13 -0
  141. package/dist/multiplayer/identity.js +20 -0
  142. package/dist/multiplayer/lagCompensation.d.ts +43 -0
  143. package/dist/multiplayer/lagCompensation.js +154 -0
  144. package/dist/multiplayer/matchmaking.d.ts +33 -0
  145. package/dist/multiplayer/matchmaking.js +60 -0
  146. package/dist/multiplayer/poseSyncGate.d.ts +1 -0
  147. package/dist/multiplayer/poseSyncGate.js +13 -1
  148. package/dist/multiplayer/presenceModel.d.ts +3 -0
  149. package/dist/multiplayer/presenceModel.js +15 -1
  150. package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
  151. package/dist/multiplayer/simultaneousCommit.js +53 -0
  152. package/dist/multiplayer/voiceContract.d.ts +44 -0
  153. package/dist/multiplayer/voiceContract.js +108 -0
  154. package/dist/nav/navConstrain.d.ts +14 -0
  155. package/dist/nav/navConstrain.js +26 -0
  156. package/dist/nav/navFromEnvironment.d.ts +11 -0
  157. package/dist/nav/navFromEnvironment.js +16 -0
  158. package/dist/nav/navGrid.d.ts +56 -0
  159. package/dist/nav/navGrid.js +265 -0
  160. package/dist/nav/pathFollow.d.ts +31 -0
  161. package/dist/nav/pathFollow.js +69 -0
  162. package/dist/physics/ballisticSweep.d.ts +18 -0
  163. package/dist/physics/ballisticSweep.js +49 -0
  164. package/dist/physics/buoyancy.d.ts +49 -0
  165. package/dist/physics/buoyancy.js +112 -0
  166. package/dist/physics/carryable.d.ts +46 -0
  167. package/dist/physics/carryable.js +110 -0
  168. package/dist/physics/damageZones.d.ts +56 -0
  169. package/dist/physics/damageZones.js +107 -0
  170. package/dist/physics/forceVolume.d.ts +48 -0
  171. package/dist/physics/forceVolume.js +119 -0
  172. package/dist/physics/physicsWorld.d.ts +124 -2
  173. package/dist/physics/physicsWorld.js +493 -41
  174. package/dist/physics/ragdoll.d.ts +49 -0
  175. package/dist/physics/ragdoll.js +103 -0
  176. package/dist/physics/spatialGrid.d.ts +41 -0
  177. package/dist/physics/spatialGrid.js +143 -0
  178. package/dist/physics/structure.d.ts +83 -0
  179. package/dist/physics/structure.js +0 -0
  180. package/dist/physics/traversal.d.ts +75 -0
  181. package/dist/physics/traversal.js +138 -0
  182. package/dist/physics/vehicleBody.d.ts +99 -0
  183. package/dist/physics/vehicleBody.js +233 -0
  184. package/dist/puzzle/cellGrid.d.ts +26 -0
  185. package/dist/puzzle/cellGrid.js +110 -0
  186. package/dist/puzzle/fallingPiece.d.ts +24 -0
  187. package/dist/puzzle/fallingPiece.js +51 -0
  188. package/dist/random/nameGen.d.ts +18 -0
  189. package/dist/random/nameGen.js +35 -0
  190. package/dist/random/rng.d.ts +2 -0
  191. package/dist/random/rng.js +23 -0
  192. package/dist/runtime/adapter.d.ts +35 -0
  193. package/dist/runtime/adapter.js +37 -1
  194. package/dist/runtime/cameraDirector.d.ts +10 -0
  195. package/dist/runtime/cameraDirector.js +25 -0
  196. package/dist/runtime/gameContext.d.ts +107 -3
  197. package/dist/runtime/gameContext.js +253 -7
  198. package/dist/runtime/hostPersistence.d.ts +39 -0
  199. package/dist/runtime/hostPersistence.js +54 -0
  200. package/dist/runtime/inputSnapshot.d.ts +7 -0
  201. package/dist/runtime/inputSnapshot.js +12 -0
  202. package/dist/runtime/motionIntents.d.ts +25 -0
  203. package/dist/runtime/motionIntents.js +25 -0
  204. package/dist/runtime/persistenceScope.d.ts +32 -0
  205. package/dist/runtime/persistenceScope.js +64 -0
  206. package/dist/runtime/transport.d.ts +32 -0
  207. package/dist/scene/autoTarget.d.ts +15 -0
  208. package/dist/scene/autoTarget.js +54 -0
  209. package/dist/scene/behaviors.d.ts +13 -1
  210. package/dist/scene/behaviors.js +3 -0
  211. package/dist/scene/captureCheck.d.ts +7 -0
  212. package/dist/scene/captureCheck.js +11 -0
  213. package/dist/scene/entityStore.d.ts +9 -1
  214. package/dist/scene/entityStore.js +25 -2
  215. package/dist/scene/form.d.ts +40 -0
  216. package/dist/scene/form.js +56 -0
  217. package/dist/scene/mount.d.ts +62 -0
  218. package/dist/scene/mount.js +107 -0
  219. package/dist/scene/movementSpeed.d.ts +35 -0
  220. package/dist/scene/movementSpeed.js +20 -0
  221. package/dist/scene/objectQuery.d.ts +18 -0
  222. package/dist/scene/objectQuery.js +108 -0
  223. package/dist/scene/objectStore.d.ts +11 -0
  224. package/dist/scene/objectStore.js +109 -4
  225. package/dist/scene/paintLayer.d.ts +15 -0
  226. package/dist/scene/paintLayer.js +53 -0
  227. package/dist/scene/possession.d.ts +30 -0
  228. package/dist/scene/possession.js +51 -0
  229. package/dist/scene/roster.d.ts +25 -0
  230. package/dist/scene/roster.js +57 -0
  231. package/dist/scene/selection.d.ts +30 -0
  232. package/dist/scene/selection.js +38 -0
  233. package/dist/scene/spatial.d.ts +21 -0
  234. package/dist/scene/spatial.js +63 -1
  235. package/dist/scene/stationClaim.d.ts +43 -0
  236. package/dist/scene/stationClaim.js +109 -0
  237. package/dist/sensor/concealment.d.ts +21 -0
  238. package/dist/sensor/concealment.js +63 -0
  239. package/dist/sensor/freezeMonitor.d.ts +17 -0
  240. package/dist/sensor/freezeMonitor.js +35 -0
  241. package/dist/sensor/frustumSensor.d.ts +60 -0
  242. package/dist/sensor/frustumSensor.js +104 -0
  243. package/dist/sensor/hiddenStateProbe.d.ts +31 -0
  244. package/dist/sensor/hiddenStateProbe.js +40 -0
  245. package/dist/sensor/recordingBuffer.d.ts +29 -0
  246. package/dist/sensor/recordingBuffer.js +61 -0
  247. package/dist/sensor/revealQuery.d.ts +22 -0
  248. package/dist/sensor/revealQuery.js +36 -0
  249. package/dist/session/contestedChannel.d.ts +36 -0
  250. package/dist/session/contestedChannel.js +100 -0
  251. package/dist/session/extraction.d.ts +54 -0
  252. package/dist/session/extraction.js +108 -0
  253. package/dist/session/ring.d.ts +36 -0
  254. package/dist/session/ring.js +88 -0
  255. package/dist/session/roles.d.ts +6 -0
  256. package/dist/session/roles.js +32 -0
  257. package/dist/session/roundState.d.ts +79 -0
  258. package/dist/session/roundState.js +158 -0
  259. package/dist/stats/accumulatorMeter.d.ts +31 -0
  260. package/dist/stats/accumulatorMeter.js +81 -0
  261. package/dist/stats/eventMeter.d.ts +34 -0
  262. package/dist/stats/eventMeter.js +76 -0
  263. package/dist/stats/rollCheck.d.ts +15 -0
  264. package/dist/stats/rollCheck.js +15 -0
  265. package/dist/survival/decayMeter.d.ts +59 -0
  266. package/dist/survival/decayMeter.js +95 -0
  267. package/dist/survival/moodle.d.ts +48 -0
  268. package/dist/survival/moodle.js +91 -0
  269. package/dist/survival/regionHealth.d.ts +72 -0
  270. package/dist/survival/regionHealth.js +129 -0
  271. package/dist/tactics/fallingGrid.d.ts +62 -0
  272. package/dist/tactics/fallingGrid.js +158 -0
  273. package/dist/tactics/predictiveQuery.d.ts +34 -0
  274. package/dist/tactics/predictiveQuery.js +44 -0
  275. package/dist/tactics/snapshot.d.ts +18 -0
  276. package/dist/tactics/snapshot.js +65 -0
  277. package/dist/tactics/surface.d.ts +52 -0
  278. package/dist/tactics/surface.js +135 -0
  279. package/dist/tactics/tacticalGrid.d.ts +68 -0
  280. package/dist/tactics/tacticalGrid.js +261 -0
  281. package/dist/time/beatClock.d.ts +40 -0
  282. package/dist/time/beatClock.js +73 -0
  283. package/dist/time/simClock.d.ts +25 -1
  284. package/dist/time/simClock.js +28 -3
  285. package/dist/turn/commit.d.ts +27 -0
  286. package/dist/turn/commit.js +72 -0
  287. package/dist/turn/intent.d.ts +14 -0
  288. package/dist/turn/intent.js +29 -0
  289. package/dist/turn/turnLoop.d.ts +60 -0
  290. package/dist/turn/turnLoop.js +223 -0
  291. package/dist/world/buildPermissions.d.ts +47 -0
  292. package/dist/world/buildPermissions.js +134 -0
  293. package/dist/world/carve.d.ts +105 -0
  294. package/dist/world/carve.js +175 -0
  295. package/dist/world/connectors.d.ts +40 -0
  296. package/dist/world/connectors.js +67 -0
  297. package/dist/world/envField.d.ts +71 -0
  298. package/dist/world/envField.js +96 -0
  299. package/dist/world/environmentSummary.d.ts +53 -0
  300. package/dist/world/environmentSummary.js +129 -0
  301. package/dist/world/features.d.ts +87 -6
  302. package/dist/world/features.js +52 -2
  303. package/dist/world/fog.d.ts +40 -0
  304. package/dist/world/fog.js +137 -0
  305. package/dist/world/gridInstances.d.ts +13 -0
  306. package/dist/world/gridInstances.js +22 -0
  307. package/dist/world/markers.d.ts +52 -0
  308. package/dist/world/markers.js +96 -0
  309. package/dist/world/minimap.d.ts +42 -0
  310. package/dist/world/minimap.js +72 -0
  311. package/dist/world/placedStructureStore.d.ts +39 -0
  312. package/dist/world/placedStructureStore.js +95 -0
  313. package/dist/world/placementController.d.ts +69 -0
  314. package/dist/world/placementController.js +153 -0
  315. package/dist/world/realm.d.ts +61 -0
  316. package/dist/world/realm.js +66 -0
  317. package/dist/world/support.d.ts +27 -0
  318. package/dist/world/support.js +75 -0
  319. package/dist/world/terraform.d.ts +54 -0
  320. package/dist/world/terraform.js +186 -0
  321. package/dist/world/terrain.d.ts +23 -1
  322. package/dist/world/terrain.js +64 -1
  323. package/dist/world/voxelField.d.ts +54 -0
  324. package/dist/world/voxelField.js +241 -0
  325. package/dist/world/walls.d.ts +61 -0
  326. package/dist/world/walls.js +221 -0
  327. package/dist/world/weather.d.ts +92 -0
  328. package/dist/world/weather.js +169 -0
  329. package/llms.txt +4102 -0
  330. package/package.json +3 -2
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Unity-style behaviour lifecycle over a headless id-keyed node tree, ported from
3
+ * vladkrutenyuk/three-start (MIT) and adapted to run without three.js: nodes are plain
4
+ * string ids (use entity instance ids to pair with `entityStore`), and the shell binds
5
+ * render-side hooks separately.
6
+ *
7
+ * Lifecycle per behaviour: `onAwake` (once, first activation) → `onEnable` (every
8
+ * inactive→active transition) → `onStart` (once, after the first `onEnable`) →
9
+ * `onUpdate` (per `world.update` while active) → `onDisable` (every active→inactive
10
+ * transition) → `onDestroy` (once, always — even if never activated).
11
+ *
12
+ * `world.start()` bootstraps in passes: modules awake → modules start → modules join
13
+ * update dispatch → ALL behaviours on effectively-active nodes awake (even disabled
14
+ * ones) → enable/start/subscribe. Behaviours attached during the module pass are
15
+ * deferred to the behaviour passes, so module `onUpdate` always dispatches before any
16
+ * behaviour `onUpdate`.
17
+ */
18
+ /**
19
+ * Declaration-merging registry games and adapters augment for typed access to
20
+ * world modules:
21
+ *
22
+ * ```ts
23
+ * declare module "@jgengine/core/behaviour/behaviour" {
24
+ * interface JGEngineRegister {
25
+ * modules: { physics: PhysicsModule; director: DirectorModule };
26
+ * }
27
+ * }
28
+ * ```
29
+ */
30
+ export interface JGEngineRegister {
31
+ }
32
+ export type RegisterField<TKey extends string, TFallback = unknown> = TKey extends keyof JGEngineRegister ? JGEngineRegister[TKey] : TFallback;
33
+ /** Resolves to the augmented module map when `JGEngineRegister` declares one. */
34
+ export type BehaviourModules = RegisterField<"modules", Record<string, BehaviourModule>>;
35
+ interface UpdateSubscriber {
36
+ onUpdate(dt: number): void;
37
+ }
38
+ interface BehaviourWorldInternal extends BehaviourWorld {
39
+ _subscribeUpdate(subscriber: UpdateSubscriber): void;
40
+ _unsubscribeUpdate(subscriber: UpdateSubscriber): void;
41
+ _isEffectivelyActive(nodeId: string): boolean;
42
+ _detachBehaviour(behaviour: Behaviour): void;
43
+ }
44
+ /**
45
+ * Subclass and override the lifecycle hooks. A behaviour only joins the per-frame
46
+ * update dispatch if it actually overrides `onUpdate` (prototype-identity check at
47
+ * each activation), so hook-only behaviours cost nothing per frame.
48
+ */
49
+ export declare class Behaviour {
50
+ /** @internal */ _world: BehaviourWorldInternal | null;
51
+ /** @internal */ _nodeId: string | null;
52
+ /** @internal */ _awoken: boolean;
53
+ /** @internal */ _startedOnce: boolean;
54
+ /** @internal */ _isActive: boolean;
55
+ private _enabled;
56
+ private _destroyed;
57
+ /** The node this behaviour is attached to; available from `onAwake` onward, never in the constructor. */
58
+ get nodeId(): string;
59
+ get world(): BehaviourWorld;
60
+ get modules(): BehaviourModules;
61
+ /** Own enabled flag; effective activity also requires the node's hierarchy to be active. */
62
+ get enabled(): boolean;
63
+ /** Whether the lifecycle is currently in the active span (`onEnable` fired, `onDisable` not yet). */
64
+ get isActive(): boolean;
65
+ onAwake(): void;
66
+ onEnable(): void;
67
+ onStart(): void;
68
+ onUpdate(_dt: number): void;
69
+ onDisable(): void;
70
+ onDestroy(): void;
71
+ enable(): void;
72
+ disable(): void;
73
+ /** Flips the flag only when the node is hierarchy-inactive or the world hasn't started; lifecycle catches up on the next activation. */
74
+ setEnabled(enabled: boolean): void;
75
+ /** Detaches from the node and fires `onDisable` (if active) then `onDestroy`. */
76
+ destroy(): void;
77
+ /** @internal */
78
+ _activate(): void;
79
+ /** @internal */
80
+ _deactivate(): void;
81
+ /** @internal */
82
+ _destroy(): void;
83
+ }
84
+ /**
85
+ * A world-lifetime service with typed sibling access via `this.modules`. Modules awake
86
+ * and start before any behaviour during `world.start()`, subscribe to update dispatch
87
+ * first (their `onUpdate` fires before every behaviour's), and have no disable/destroy —
88
+ * they live as long as the world.
89
+ */
90
+ export declare class BehaviourModule {
91
+ /** @internal */ _world: BehaviourWorldInternal | null;
92
+ get world(): BehaviourWorld;
93
+ get modules(): BehaviourModules;
94
+ onAwake(): void;
95
+ onStart(): void;
96
+ onUpdate(_dt: number): void;
97
+ /** @internal */
98
+ _subscribe(): void;
99
+ }
100
+ export interface BehaviourWorld {
101
+ /** Creates the node if missing (parented under `parentId` when given, else a root); reparents an existing node when `parentId` differs. */
102
+ add(id: string, parentId?: string): void;
103
+ has(id: string): boolean;
104
+ parentOf(id: string): string | null;
105
+ childrenOf(id: string): readonly string[];
106
+ /** Moves `id` (and subtree) under `parentId` (`null` = root), firing enable/disable cascades when effective activity changes. Throws on cycles. */
107
+ setParent(id: string, parentId: string | null): void;
108
+ /** Attaches and immediately activates when the world has started, the node is effectively active, and the behaviour is enabled (deferred during bootstrap). */
109
+ attach<T extends Behaviour>(nodeId: string, behaviour: T): T;
110
+ behavioursOf(nodeId: string): readonly Behaviour[];
111
+ /**
112
+ * Sets the node's own flag and cascades activation/deactivation through the subtree,
113
+ * pruning at descendants whose own flag is false — so reactivating a parent restores
114
+ * exactly the per-child stored flags. A flip under an inactive ancestor changes only
115
+ * the flag.
116
+ */
117
+ setActive(id: string, active: boolean): void;
118
+ /** The node's own flag; unknown ids count active. */
119
+ isActiveSelf(id: string): boolean;
120
+ /** Own flag AND every ancestor's; unknown ids count active. */
121
+ isActive(id: string): boolean;
122
+ /** Registers modules by key. Throws on duplicate keys or after `start()`. */
123
+ addModules(modules: Partial<BehaviourModules>): void;
124
+ readonly modules: BehaviourModules;
125
+ /** Bootstraps: modules awake/start/subscribe, then all behaviours on effectively-active nodes awake, then enable+start+subscribe. Idempotent. */
126
+ start(): void;
127
+ started(): boolean;
128
+ /** Dispatches `onUpdate(dt)` to modules then behaviours, over a snapshot — subscribers added mid-dispatch first fire next update. */
129
+ update(dt: number): void;
130
+ /** Destroys every behaviour in the subtree (parent-first) and removes the nodes. */
131
+ destroyNode(id: string): boolean;
132
+ /** Destroys all nodes and behaviours and clears update dispatch. */
133
+ destroy(): void;
134
+ }
135
+ export declare function createBehaviourWorld(): BehaviourWorld;
136
+ export {};
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Unity-style behaviour lifecycle over a headless id-keyed node tree, ported from
3
+ * vladkrutenyuk/three-start (MIT) and adapted to run without three.js: nodes are plain
4
+ * string ids (use entity instance ids to pair with `entityStore`), and the shell binds
5
+ * render-side hooks separately.
6
+ *
7
+ * Lifecycle per behaviour: `onAwake` (once, first activation) → `onEnable` (every
8
+ * inactive→active transition) → `onStart` (once, after the first `onEnable`) →
9
+ * `onUpdate` (per `world.update` while active) → `onDisable` (every active→inactive
10
+ * transition) → `onDestroy` (once, always — even if never activated).
11
+ *
12
+ * `world.start()` bootstraps in passes: modules awake → modules start → modules join
13
+ * update dispatch → ALL behaviours on effectively-active nodes awake (even disabled
14
+ * ones) → enable/start/subscribe. Behaviours attached during the module pass are
15
+ * deferred to the behaviour passes, so module `onUpdate` always dispatches before any
16
+ * behaviour `onUpdate`.
17
+ */
18
+ /**
19
+ * Subclass and override the lifecycle hooks. A behaviour only joins the per-frame
20
+ * update dispatch if it actually overrides `onUpdate` (prototype-identity check at
21
+ * each activation), so hook-only behaviours cost nothing per frame.
22
+ */
23
+ export class Behaviour {
24
+ /** @internal */ _world = null;
25
+ /** @internal */ _nodeId = null;
26
+ /** @internal */ _awoken = false;
27
+ /** @internal */ _startedOnce = false;
28
+ /** @internal */ _isActive = false;
29
+ _enabled = true;
30
+ _destroyed = false;
31
+ /** The node this behaviour is attached to; available from `onAwake` onward, never in the constructor. */
32
+ get nodeId() {
33
+ if (this._nodeId === null)
34
+ throw new Error("behaviour is not attached to a node");
35
+ return this._nodeId;
36
+ }
37
+ get world() {
38
+ if (this._world === null)
39
+ throw new Error("behaviour is not attached to a world");
40
+ return this._world;
41
+ }
42
+ get modules() {
43
+ return this.world.modules;
44
+ }
45
+ /** Own enabled flag; effective activity also requires the node's hierarchy to be active. */
46
+ get enabled() {
47
+ return this._enabled;
48
+ }
49
+ /** Whether the lifecycle is currently in the active span (`onEnable` fired, `onDisable` not yet). */
50
+ get isActive() {
51
+ return this._isActive;
52
+ }
53
+ onAwake() { }
54
+ onEnable() { }
55
+ onStart() { }
56
+ onUpdate(_dt) { }
57
+ onDisable() { }
58
+ onDestroy() { }
59
+ enable() {
60
+ this.setEnabled(true);
61
+ }
62
+ disable() {
63
+ this.setEnabled(false);
64
+ }
65
+ /** Flips the flag only when the node is hierarchy-inactive or the world hasn't started; lifecycle catches up on the next activation. */
66
+ setEnabled(enabled) {
67
+ if (this._enabled === enabled)
68
+ return;
69
+ this._enabled = enabled;
70
+ const world = this._world;
71
+ const nodeId = this._nodeId;
72
+ if (world === null || nodeId === null)
73
+ return;
74
+ if (!world.started() || !world._isEffectivelyActive(nodeId))
75
+ return;
76
+ if (enabled)
77
+ this._activate();
78
+ else
79
+ this._deactivate();
80
+ }
81
+ /** Detaches from the node and fires `onDisable` (if active) then `onDestroy`. */
82
+ destroy() {
83
+ if (this._world !== null)
84
+ this._world._detachBehaviour(this);
85
+ this._destroy();
86
+ }
87
+ /** @internal */
88
+ _activate() {
89
+ if (this._isActive)
90
+ return;
91
+ if (this._world === null)
92
+ return;
93
+ this._isActive = true;
94
+ if (!this._awoken) {
95
+ this._awoken = true;
96
+ this.onAwake();
97
+ }
98
+ this.onEnable();
99
+ if (!this._startedOnce) {
100
+ this._startedOnce = true;
101
+ this.onStart();
102
+ }
103
+ if (this.onUpdate !== Behaviour.prototype.onUpdate) {
104
+ this._world._subscribeUpdate(this);
105
+ }
106
+ }
107
+ /** @internal */
108
+ _deactivate() {
109
+ if (!this._isActive)
110
+ return;
111
+ this._isActive = false;
112
+ this._world?._unsubscribeUpdate(this);
113
+ this.onDisable();
114
+ }
115
+ /** @internal */
116
+ _destroy() {
117
+ if (this._destroyed)
118
+ return;
119
+ this._destroyed = true;
120
+ this._deactivate();
121
+ this.onDestroy();
122
+ this._world = null;
123
+ this._nodeId = null;
124
+ }
125
+ }
126
+ /**
127
+ * A world-lifetime service with typed sibling access via `this.modules`. Modules awake
128
+ * and start before any behaviour during `world.start()`, subscribe to update dispatch
129
+ * first (their `onUpdate` fires before every behaviour's), and have no disable/destroy —
130
+ * they live as long as the world.
131
+ */
132
+ export class BehaviourModule {
133
+ /** @internal */ _world = null;
134
+ get world() {
135
+ if (this._world === null)
136
+ throw new Error("module is not registered with a world");
137
+ return this._world;
138
+ }
139
+ get modules() {
140
+ return this.world.modules;
141
+ }
142
+ onAwake() { }
143
+ onStart() { }
144
+ onUpdate(_dt) { }
145
+ /** @internal */
146
+ _subscribe() {
147
+ if (this._world === null)
148
+ return;
149
+ if (this.onUpdate !== BehaviourModule.prototype.onUpdate) {
150
+ this._world._subscribeUpdate(this);
151
+ }
152
+ }
153
+ }
154
+ export function createBehaviourWorld() {
155
+ const nodes = new Map();
156
+ const roots = [];
157
+ const updateSubscribers = [];
158
+ const moduleRecord = {};
159
+ let started = false;
160
+ let bootstrapping = false;
161
+ function mustGet(id) {
162
+ const node = nodes.get(id);
163
+ if (node === undefined)
164
+ throw new Error(`unknown behaviour node "${id}"`);
165
+ return node;
166
+ }
167
+ function ensureNode(id, parentId) {
168
+ const existing = nodes.get(id);
169
+ if (existing !== undefined) {
170
+ if (parentId !== undefined && existing.parent?.id !== parentId)
171
+ world.setParent(id, parentId);
172
+ return existing;
173
+ }
174
+ const parent = parentId === undefined ? null : ensureNode(parentId);
175
+ const node = { id, parent, children: [], activeSelf: true, behaviours: [] };
176
+ nodes.set(id, node);
177
+ if (parent === null)
178
+ roots.push(node);
179
+ else
180
+ parent.children.push(node);
181
+ return node;
182
+ }
183
+ function parentChainActive(node) {
184
+ let ancestor = node.parent;
185
+ while (ancestor !== null) {
186
+ if (!ancestor.activeSelf)
187
+ return false;
188
+ ancestor = ancestor.parent;
189
+ }
190
+ return true;
191
+ }
192
+ function isEffectivelyActive(node) {
193
+ return node.activeSelf && parentChainActive(node);
194
+ }
195
+ function traverseActiveSelf(node, visit) {
196
+ if (!node.activeSelf)
197
+ return;
198
+ visit(node);
199
+ for (const child of [...node.children])
200
+ traverseActiveSelf(child, visit);
201
+ }
202
+ function activateNode(node) {
203
+ if (!started)
204
+ return;
205
+ if (!isEffectivelyActive(node))
206
+ return;
207
+ for (const behaviour of [...node.behaviours]) {
208
+ if (behaviour.enabled)
209
+ behaviour._activate();
210
+ }
211
+ }
212
+ function deactivateNode(node) {
213
+ for (const behaviour of [...node.behaviours]) {
214
+ if (behaviour._isActive)
215
+ behaviour._deactivate();
216
+ }
217
+ }
218
+ function deactivateSubtree(node) {
219
+ deactivateNode(node);
220
+ for (const child of [...node.children])
221
+ traverseActiveSelf(child, deactivateNode);
222
+ }
223
+ function unlink(node) {
224
+ const siblings = node.parent === null ? roots : node.parent.children;
225
+ const index = siblings.indexOf(node);
226
+ if (index !== -1)
227
+ siblings.splice(index, 1);
228
+ }
229
+ function collectSubtree(node, into) {
230
+ into.push(node);
231
+ for (const child of node.children)
232
+ collectSubtree(child, into);
233
+ }
234
+ const world = {
235
+ add(id, parentId) {
236
+ ensureNode(id, parentId);
237
+ },
238
+ has: (id) => nodes.has(id),
239
+ parentOf: (id) => mustGet(id).parent?.id ?? null,
240
+ childrenOf: (id) => mustGet(id).children.map((child) => child.id),
241
+ setParent(id, parentId) {
242
+ const node = mustGet(id);
243
+ const nextParent = parentId === null ? null : mustGet(parentId);
244
+ if (node.parent === nextParent)
245
+ return;
246
+ for (let ancestor = nextParent; ancestor !== null; ancestor = ancestor.parent) {
247
+ if (ancestor === node)
248
+ throw new Error(`cannot parent "${parentId}" under its own descendant "${id}"`);
249
+ }
250
+ const wasEffective = isEffectivelyActive(node);
251
+ unlink(node);
252
+ node.parent = nextParent;
253
+ if (nextParent === null)
254
+ roots.push(node);
255
+ else
256
+ nextParent.children.push(node);
257
+ const nowEffective = isEffectivelyActive(node);
258
+ if (!started || wasEffective === nowEffective)
259
+ return;
260
+ if (nowEffective)
261
+ traverseActiveSelf(node, activateNode);
262
+ else
263
+ deactivateSubtree(node);
264
+ },
265
+ attach(nodeId, behaviour) {
266
+ if (behaviour._world !== null)
267
+ throw new Error("behaviour is already attached");
268
+ const node = ensureNode(nodeId);
269
+ behaviour._world = world;
270
+ behaviour._nodeId = nodeId;
271
+ node.behaviours.push(behaviour);
272
+ if (started && !bootstrapping && behaviour.enabled && isEffectivelyActive(node)) {
273
+ behaviour._activate();
274
+ }
275
+ return behaviour;
276
+ },
277
+ behavioursOf: (nodeId) => [...(nodes.get(nodeId)?.behaviours ?? [])],
278
+ setActive(id, active) {
279
+ const node = ensureNode(id);
280
+ if (node.activeSelf === active)
281
+ return;
282
+ node.activeSelf = active;
283
+ if (!parentChainActive(node))
284
+ return;
285
+ if (active)
286
+ traverseActiveSelf(node, activateNode);
287
+ else
288
+ deactivateSubtree(node);
289
+ },
290
+ isActiveSelf: (id) => nodes.get(id)?.activeSelf ?? true,
291
+ isActive(id) {
292
+ const node = nodes.get(id);
293
+ return node === undefined ? true : isEffectivelyActive(node);
294
+ },
295
+ addModules(modules) {
296
+ if (started)
297
+ throw new Error("cannot add modules after start()");
298
+ for (const [key, module] of Object.entries(modules)) {
299
+ if (module === undefined)
300
+ continue;
301
+ if (moduleRecord[key] !== undefined)
302
+ throw new Error(`module "${key}" is already registered`);
303
+ const instance = module;
304
+ instance._world = world;
305
+ moduleRecord[key] = instance;
306
+ }
307
+ },
308
+ modules: moduleRecord,
309
+ start() {
310
+ if (started)
311
+ return;
312
+ started = true;
313
+ bootstrapping = true;
314
+ const modules = Object.values(moduleRecord);
315
+ for (const module of modules)
316
+ module.onAwake();
317
+ for (const module of modules)
318
+ module.onStart();
319
+ for (const module of modules)
320
+ module._subscribe();
321
+ bootstrapping = false;
322
+ for (const root of [...roots]) {
323
+ traverseActiveSelf(root, (node) => {
324
+ for (const behaviour of [...node.behaviours]) {
325
+ if (!behaviour._awoken) {
326
+ behaviour._awoken = true;
327
+ behaviour.onAwake();
328
+ }
329
+ }
330
+ });
331
+ }
332
+ for (const root of [...roots]) {
333
+ traverseActiveSelf(root, activateNode);
334
+ }
335
+ },
336
+ started: () => started,
337
+ update(dt) {
338
+ for (const subscriber of [...updateSubscribers])
339
+ subscriber.onUpdate(dt);
340
+ },
341
+ destroyNode(id) {
342
+ const node = nodes.get(id);
343
+ if (node === undefined)
344
+ return false;
345
+ const subtree = [];
346
+ collectSubtree(node, subtree);
347
+ for (const member of subtree) {
348
+ for (const behaviour of [...member.behaviours])
349
+ behaviour._destroy();
350
+ member.behaviours.length = 0;
351
+ }
352
+ unlink(node);
353
+ for (const member of subtree)
354
+ nodes.delete(member.id);
355
+ return true;
356
+ },
357
+ destroy() {
358
+ for (const root of [...roots])
359
+ world.destroyNode(root.id);
360
+ updateSubscribers.length = 0;
361
+ },
362
+ _subscribeUpdate(subscriber) {
363
+ if (!updateSubscribers.includes(subscriber))
364
+ updateSubscribers.push(subscriber);
365
+ },
366
+ _unsubscribeUpdate(subscriber) {
367
+ const index = updateSubscribers.indexOf(subscriber);
368
+ if (index !== -1)
369
+ updateSubscribers.splice(index, 1);
370
+ },
371
+ _isEffectivelyActive: (nodeId) => {
372
+ const node = nodes.get(nodeId);
373
+ return node === undefined ? true : isEffectivelyActive(node);
374
+ },
375
+ _detachBehaviour(behaviour) {
376
+ const nodeId = behaviour._nodeId;
377
+ if (nodeId === null)
378
+ return;
379
+ const node = nodes.get(nodeId);
380
+ if (node === undefined)
381
+ return;
382
+ const index = node.behaviours.indexOf(behaviour);
383
+ if (index !== -1)
384
+ node.behaviours.splice(index, 1);
385
+ },
386
+ };
387
+ return world;
388
+ }
@@ -0,0 +1,63 @@
1
+ export type Side = string;
2
+ export interface LaneRuleInput<C> {
3
+ lane: number;
4
+ side: Side;
5
+ cards: readonly C[];
6
+ subtotal: number;
7
+ }
8
+ export interface LaneRule<C> {
9
+ id: string;
10
+ apply: (input: LaneRuleInput<C>) => number;
11
+ }
12
+ export interface LaneBoardConfig<C> {
13
+ laneCount: number;
14
+ sides: readonly Side[];
15
+ power: (card: C) => number;
16
+ laneRules?: readonly (LaneRule<C> | null | undefined)[];
17
+ }
18
+ export interface LaneCells<C> {
19
+ readonly [side: Side]: readonly C[];
20
+ }
21
+ export interface LaneBoardState<C> {
22
+ readonly lanes: readonly LaneCells<C>[];
23
+ }
24
+ export interface LaneAggregate {
25
+ lane: number;
26
+ side: Side;
27
+ subtotal: number;
28
+ total: number;
29
+ count: number;
30
+ }
31
+ export interface LaneOutcome {
32
+ lane: number;
33
+ totals: Readonly<Record<Side, number>>;
34
+ winner: Side | null;
35
+ }
36
+ export type LaneRejection = "invalid-lane" | "invalid-side";
37
+ export interface LanePlaceResult<C> {
38
+ status: "ok";
39
+ state: LaneBoardState<C>;
40
+ }
41
+ export interface LanePlaceRejected {
42
+ status: "rejected";
43
+ reason: LaneRejection;
44
+ }
45
+ export type LaneResult<C> = LanePlaceResult<C> | LanePlaceRejected;
46
+ export declare function createLaneBoardState<C>(config: LaneBoardConfig<C>): LaneBoardState<C>;
47
+ export declare function placeCard<C>(state: LaneBoardState<C>, config: LaneBoardConfig<C>, lane: number, side: Side, card: C): LaneResult<C>;
48
+ export declare function removeCard<C>(state: LaneBoardState<C>, lane: number, side: Side, predicate: (card: C, index: number) => boolean): LaneResult<C>;
49
+ export declare function laneAggregate<C>(state: LaneBoardState<C>, config: LaneBoardConfig<C>, lane: number, side: Side): LaneAggregate;
50
+ export declare function laneOutcome<C>(state: LaneBoardState<C>, config: LaneBoardConfig<C>, lane: number): LaneOutcome;
51
+ export declare function boardTotals<C>(state: LaneBoardState<C>, config: LaneBoardConfig<C>): Readonly<Record<Side, number>>;
52
+ export declare function lanesWon<C>(state: LaneBoardState<C>, config: LaneBoardConfig<C>): Readonly<Record<Side, number>>;
53
+ export interface LaneBoard<C> {
54
+ state(): LaneBoardState<C>;
55
+ place(lane: number, side: Side, card: C): LaneResult<C>;
56
+ remove(lane: number, side: Side, predicate: (card: C, index: number) => boolean): LaneResult<C>;
57
+ aggregate(lane: number, side: Side): LaneAggregate;
58
+ outcome(lane: number): LaneOutcome;
59
+ totals(): Readonly<Record<Side, number>>;
60
+ lanesWon(): Readonly<Record<Side, number>>;
61
+ reset(next?: LaneBoardState<C>): void;
62
+ }
63
+ export declare function createLaneBoard<C>(config: LaneBoardConfig<C>): LaneBoard<C>;
@@ -0,0 +1,114 @@
1
+ function emptyLane(sides) {
2
+ const cells = {};
3
+ for (const side of sides)
4
+ cells[side] = [];
5
+ return cells;
6
+ }
7
+ export function createLaneBoardState(config) {
8
+ if (config.laneCount <= 0)
9
+ throw new Error("laneBoard needs at least one lane");
10
+ if (config.sides.length === 0)
11
+ throw new Error("laneBoard needs at least one side");
12
+ const lanes = [];
13
+ for (let i = 0; i < config.laneCount; i++)
14
+ lanes.push(emptyLane(config.sides));
15
+ return { lanes };
16
+ }
17
+ function validLane(state, lane) {
18
+ return Number.isInteger(lane) && lane >= 0 && lane < state.lanes.length;
19
+ }
20
+ export function placeCard(state, config, lane, side, card) {
21
+ if (!validLane(state, lane))
22
+ return { status: "rejected", reason: "invalid-lane" };
23
+ if (!config.sides.includes(side))
24
+ return { status: "rejected", reason: "invalid-side" };
25
+ const lanes = state.lanes.slice();
26
+ const cell = lanes[lane];
27
+ lanes[lane] = { ...cell, [side]: [...cell[side], card] };
28
+ return { status: "ok", state: { lanes } };
29
+ }
30
+ export function removeCard(state, lane, side, predicate) {
31
+ if (!validLane(state, lane))
32
+ return { status: "rejected", reason: "invalid-lane" };
33
+ const cell = state.lanes[lane];
34
+ if (cell[side] === undefined)
35
+ return { status: "rejected", reason: "invalid-side" };
36
+ const lanes = state.lanes.slice();
37
+ lanes[lane] = { ...cell, [side]: cell[side].filter((card, index) => !predicate(card, index)) };
38
+ return { status: "ok", state: { lanes } };
39
+ }
40
+ export function laneAggregate(state, config, lane, side) {
41
+ const cards = state.lanes[lane]?.[side] ?? [];
42
+ let subtotal = 0;
43
+ for (const card of cards)
44
+ subtotal += config.power(card);
45
+ const rule = config.laneRules?.[lane] ?? null;
46
+ const total = rule ? rule.apply({ lane, side, cards, subtotal }) : subtotal;
47
+ return { lane, side, subtotal, total, count: cards.length };
48
+ }
49
+ export function laneOutcome(state, config, lane) {
50
+ const totals = {};
51
+ let winner = null;
52
+ let best = -Infinity;
53
+ let tie = false;
54
+ for (const side of config.sides) {
55
+ const total = laneAggregate(state, config, lane, side).total;
56
+ totals[side] = total;
57
+ if (total > best) {
58
+ best = total;
59
+ winner = side;
60
+ tie = false;
61
+ }
62
+ else if (total === best) {
63
+ tie = true;
64
+ }
65
+ }
66
+ return { lane, totals, winner: tie ? null : winner };
67
+ }
68
+ export function boardTotals(state, config) {
69
+ const totals = {};
70
+ for (const side of config.sides)
71
+ totals[side] = 0;
72
+ for (let lane = 0; lane < state.lanes.length; lane++) {
73
+ for (const side of config.sides) {
74
+ totals[side] += laneAggregate(state, config, lane, side).total;
75
+ }
76
+ }
77
+ return totals;
78
+ }
79
+ export function lanesWon(state, config) {
80
+ const won = {};
81
+ for (const side of config.sides)
82
+ won[side] = 0;
83
+ for (let lane = 0; lane < state.lanes.length; lane++) {
84
+ const outcome = laneOutcome(state, config, lane);
85
+ if (outcome.winner !== null)
86
+ won[outcome.winner] += 1;
87
+ }
88
+ return won;
89
+ }
90
+ export function createLaneBoard(config) {
91
+ let state = createLaneBoardState(config);
92
+ return {
93
+ state: () => state,
94
+ place(lane, side, card) {
95
+ const result = placeCard(state, config, lane, side, card);
96
+ if (result.status === "ok")
97
+ state = result.state;
98
+ return result;
99
+ },
100
+ remove(lane, side, predicate) {
101
+ const result = removeCard(state, lane, side, predicate);
102
+ if (result.status === "ok")
103
+ state = result.state;
104
+ return result;
105
+ },
106
+ aggregate: (lane, side) => laneAggregate(state, config, lane, side),
107
+ outcome: (lane) => laneOutcome(state, config, lane),
108
+ totals: () => boardTotals(state, config),
109
+ lanesWon: () => lanesWon(state, config),
110
+ reset(next) {
111
+ state = next ?? createLaneBoardState(config);
112
+ },
113
+ };
114
+ }