@jgengine/core 0.18.0 → 0.19.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 (318) hide show
  1. package/CHANGELOG.md +265 -2
  2. package/dist/ai/decisionGraph.d.ts +96 -0
  3. package/dist/ai/decisionGraph.js +212 -0
  4. package/dist/ai/groupAssist.d.ts +10 -0
  5. package/dist/ai/groupAssist.js +11 -0
  6. package/dist/ai/mobBrainGraph.d.ts +3 -0
  7. package/dist/ai/mobBrainGraph.js +25 -0
  8. package/dist/ai/pursuit.d.ts +1 -1
  9. package/dist/ai/pursuit.js +1 -1
  10. package/dist/ai/tacticalQueries.d.ts +30 -0
  11. package/dist/ai/tacticalQueries.js +45 -0
  12. package/dist/ai/threat.d.ts +8 -0
  13. package/dist/ai/threat.js +10 -0
  14. package/dist/anim/animGraph.d.ts +155 -0
  15. package/dist/anim/animGraph.js +414 -0
  16. package/dist/anim/boneTexture.d.ts +55 -0
  17. package/dist/anim/boneTexture.js +43 -0
  18. package/dist/anim/footPlacement.d.ts +57 -0
  19. package/dist/anim/footPlacement.js +67 -0
  20. package/dist/anim/ikSolver.d.ts +51 -0
  21. package/dist/anim/ikSolver.js +124 -0
  22. package/dist/anim/locomotionGraph.d.ts +35 -0
  23. package/dist/anim/locomotionGraph.js +62 -0
  24. package/dist/assets/spriteAtlas.d.ts +21 -0
  25. package/dist/assets/spriteAtlas.js +103 -0
  26. package/dist/audio/audioFalloff.d.ts +12 -0
  27. package/dist/audio/doppler.d.ts +21 -0
  28. package/dist/audio/doppler.js +32 -0
  29. package/dist/audio/engineLayers.d.ts +83 -0
  30. package/dist/audio/engineLayers.js +115 -0
  31. package/dist/combat/abilityKit.d.ts +1 -0
  32. package/dist/combat/abilityKit.js +1 -0
  33. package/dist/combat/death.d.ts +7 -0
  34. package/dist/combat/shotOrigin.d.ts +6 -0
  35. package/dist/combat/shotOrigin.js +6 -0
  36. package/dist/combat/weaponFeedback.d.ts +36 -0
  37. package/dist/combat/weaponFeedback.js +42 -0
  38. package/dist/combat/weaponHandling.d.ts +161 -0
  39. package/dist/combat/weaponHandling.js +202 -0
  40. package/dist/combat/weaponPresentation.d.ts +115 -0
  41. package/dist/combat/weaponPresentation.js +96 -0
  42. package/dist/devtools/warnOnce.d.ts +8 -0
  43. package/dist/devtools/warnOnce.js +18 -0
  44. package/dist/economy/currency.d.ts +16 -2
  45. package/dist/economy/currency.js +55 -14
  46. package/dist/economy/wallet.d.ts +5 -4
  47. package/dist/economy/wallet.js +8 -4
  48. package/dist/editor/commandApply.js +1 -0
  49. package/dist/editor/commands.d.ts +4 -1
  50. package/dist/editor/document.d.ts +2 -1
  51. package/dist/editor/document.js +92 -1
  52. package/dist/editor/environment.d.ts +3 -0
  53. package/dist/editor/environment.js +24 -0
  54. package/dist/editor/index.d.ts +4 -2
  55. package/dist/editor/index.js +1 -1
  56. package/dist/editor/types.d.ts +20 -0
  57. package/dist/faction/reputation.d.ts +6 -0
  58. package/dist/faction/reputation.js +11 -0
  59. package/dist/game/cameraConfig.d.ts +59 -1
  60. package/dist/game/chat.d.ts +22 -1
  61. package/dist/game/chat.js +48 -19
  62. package/dist/game/controlGate.d.ts +6 -0
  63. package/dist/game/controlGate.js +30 -1
  64. package/dist/game/defineGame.d.ts +10 -1
  65. package/dist/game/events.d.ts +14 -1
  66. package/dist/game/lootTable.d.ts +4 -0
  67. package/dist/game/lootTable.js +16 -0
  68. package/dist/game/pixelPerfect.d.ts +25 -0
  69. package/dist/game/pixelPerfect.js +29 -0
  70. package/dist/game/playableGame.d.ts +167 -6
  71. package/dist/game/systemRuntime.js +6 -1
  72. package/dist/game/toasts.d.ts +8 -0
  73. package/dist/game/toasts.js +7 -0
  74. package/dist/game/vfxInstance.d.ts +9 -0
  75. package/dist/game/vfxInstance.js +12 -1
  76. package/dist/game/viewports.d.ts +43 -0
  77. package/dist/game/viewports.js +46 -0
  78. package/dist/game/worldItem.d.ts +5 -1
  79. package/dist/game/worldItem.js +0 -1
  80. package/dist/input/actionBindings.d.ts +17 -2
  81. package/dist/input/actionBindings.js +24 -11
  82. package/dist/input/actionContexts.d.ts +45 -0
  83. package/dist/input/actionContexts.js +113 -0
  84. package/dist/input/axisInput.d.ts +4 -0
  85. package/dist/input/axisInput.js +4 -0
  86. package/dist/input/axisShaper.d.ts +73 -0
  87. package/dist/input/axisShaper.js +105 -0
  88. package/dist/input/gamepadModel.d.ts +79 -0
  89. package/dist/input/gamepadModel.js +108 -0
  90. package/dist/input/haptics.d.ts +50 -0
  91. package/dist/input/haptics.js +74 -0
  92. package/dist/input/inputBuffer.d.ts +40 -0
  93. package/dist/input/inputBuffer.js +59 -0
  94. package/dist/input/lookChannel.d.ts +10 -0
  95. package/dist/input/lookChannel.js +6 -0
  96. package/dist/input/pointer.d.ts +2 -0
  97. package/dist/input/pointer.js +4 -0
  98. package/dist/item/affix.d.ts +6 -0
  99. package/dist/item/affix.js +6 -0
  100. package/dist/meta/changelog.d.ts +1 -1
  101. package/dist/meta/changelog.js +196 -1
  102. package/dist/movement/characterController.d.ts +76 -0
  103. package/dist/movement/characterController.js +216 -0
  104. package/dist/movement/freeFlight.d.ts +119 -0
  105. package/dist/movement/freeFlight.js +294 -0
  106. package/dist/movement/glideModel.d.ts +3 -0
  107. package/dist/movement/glideModel.js +6 -0
  108. package/dist/movement/grappleSwing.d.ts +2 -0
  109. package/dist/movement/grappleSwing.js +5 -1
  110. package/dist/movement/movementModel.d.ts +39 -0
  111. package/dist/movement/movementModel.js +62 -19
  112. package/dist/movement/movementProbe.d.ts +51 -0
  113. package/dist/movement/movementProbe.js +119 -0
  114. package/dist/movement/playerMovement.d.ts +30 -3
  115. package/dist/movement/playerMovement.js +304 -7
  116. package/dist/movement/serverStep.d.ts +5 -0
  117. package/dist/movement/serverStep.js +6 -0
  118. package/dist/movement/solidObstacles.d.ts +39 -8
  119. package/dist/movement/solidObstacles.js +71 -6
  120. package/dist/movement/voxelController.js +7 -7
  121. package/dist/multiplayer/poseSyncGate.d.ts +3 -1
  122. package/dist/multiplayer/poseSyncGate.js +9 -1
  123. package/dist/multiplayer/presenceContract.d.ts +6 -0
  124. package/dist/multiplayer/presenceModel.d.ts +2 -0
  125. package/dist/multiplayer/presenceModel.js +11 -0
  126. package/dist/nav/navFromEnvironment.d.ts +9 -3
  127. package/dist/nav/navFromEnvironment.js +18 -12
  128. package/dist/nav/navMesh.d.ts +74 -0
  129. package/dist/nav/navMesh.js +570 -0
  130. package/dist/physics/aircraftDynamics.d.ts +390 -0
  131. package/dist/physics/aircraftDynamics.js +704 -0
  132. package/dist/physics/boatDynamics.d.ts +116 -0
  133. package/dist/physics/boatDynamics.js +160 -0
  134. package/dist/physics/drivableVehicle.d.ts +22 -8
  135. package/dist/physics/drivableVehicle.js +4 -2
  136. package/dist/physics/flightDynamics.d.ts +29 -4
  137. package/dist/physics/flightDynamics.js +28 -5
  138. package/dist/physics/handlingProbe.d.ts +249 -0
  139. package/dist/physics/handlingProbe.js +606 -0
  140. package/dist/physics/kinematicVehicle.d.ts +37 -2
  141. package/dist/physics/kinematicVehicle.js +15 -0
  142. package/dist/physics/physicsBackend.d.ts +203 -0
  143. package/dist/physics/physicsBackend.js +49 -0
  144. package/dist/physics/physicsBackendConformance.d.ts +27 -0
  145. package/dist/physics/physicsBackendConformance.js +151 -0
  146. package/dist/physics/physicsWorldBackend.d.ts +45 -0
  147. package/dist/physics/physicsWorldBackend.js +518 -0
  148. package/dist/physics/vehicleBackendLink.d.ts +49 -0
  149. package/dist/physics/vehicleBackendLink.js +102 -0
  150. package/dist/physics/vehicleDynamics.d.ts +374 -0
  151. package/dist/physics/vehicleDynamics.js +737 -0
  152. package/dist/physics/worldColliders.d.ts +13 -0
  153. package/dist/physics/worldColliders.js +128 -0
  154. package/dist/puzzle/cellGrid.d.ts +15 -9
  155. package/dist/puzzle/cellGrid.js +13 -9
  156. package/dist/random/rng.d.ts +12 -1
  157. package/dist/random/rng.js +20 -3
  158. package/dist/render/environment.d.ts +23 -0
  159. package/dist/render/environment.js +1 -0
  160. package/dist/render/lookPreset.d.ts +19 -6
  161. package/dist/render/lookPreset.js +56 -4
  162. package/dist/render/postProcessing.d.ts +40 -1
  163. package/dist/render/postProcessing.js +1 -1
  164. package/dist/render/sprite2d.d.ts +38 -0
  165. package/dist/render/sprite2d.js +40 -0
  166. package/dist/runtime/adapter.d.ts +20 -1
  167. package/dist/runtime/adapter.js +8 -1
  168. package/dist/runtime/cameraDirector.d.ts +17 -3
  169. package/dist/runtime/cameraDirector.js +25 -0
  170. package/dist/runtime/commandInput.d.ts +52 -0
  171. package/dist/runtime/commandInput.js +71 -0
  172. package/dist/runtime/commandRunner.d.ts +39 -7
  173. package/dist/runtime/commandRunner.js +35 -5
  174. package/dist/runtime/context/combat.d.ts +1 -0
  175. package/dist/runtime/context/combat.js +2 -1
  176. package/dist/runtime/context/deathLoot.d.ts +2 -0
  177. package/dist/runtime/context/deathLoot.js +1 -0
  178. package/dist/runtime/gameContext.js +18 -1
  179. package/dist/runtime/gameContextTypes.d.ts +33 -7
  180. package/dist/runtime/gameRuntime.d.ts +23 -5
  181. package/dist/runtime/gameRuntime.js +21 -4
  182. package/dist/runtime/headlessRunner.d.ts +10 -0
  183. package/dist/runtime/headlessRunner.js +26 -8
  184. package/dist/runtime/hostPolicy.d.ts +3 -2
  185. package/dist/runtime/hostPolicy.js +8 -10
  186. package/dist/runtime/hostedGameRunner.js +34 -6
  187. package/dist/runtime/hostedWorldSession.d.ts +12 -16
  188. package/dist/runtime/hostedWorldSession.js +29 -6
  189. package/dist/runtime/hostedWorldStore.d.ts +20 -0
  190. package/dist/runtime/hostedWorldStore.js +1 -0
  191. package/dist/runtime/inputRecorder.d.ts +30 -0
  192. package/dist/runtime/inputRecorder.js +59 -0
  193. package/dist/runtime/inputSnapshot.d.ts +14 -0
  194. package/dist/runtime/inputSnapshot.js +11 -0
  195. package/dist/runtime/localPlayers.d.ts +73 -0
  196. package/dist/runtime/localPlayers.js +119 -0
  197. package/dist/runtime/poseInterpolation.d.ts +36 -0
  198. package/dist/runtime/poseInterpolation.js +91 -0
  199. package/dist/runtime/prediction.d.ts +45 -0
  200. package/dist/runtime/prediction.js +62 -0
  201. package/dist/runtime/simContext.d.ts +45 -0
  202. package/dist/runtime/simContext.js +65 -0
  203. package/dist/runtime/simLoop.d.ts +52 -0
  204. package/dist/runtime/simLoop.js +72 -0
  205. package/dist/runtime/simSnapshot.d.ts +32 -0
  206. package/dist/runtime/simSnapshot.js +60 -0
  207. package/dist/runtime/snapshot.d.ts +8 -0
  208. package/dist/runtime/snapshotBuffer.d.ts +33 -0
  209. package/dist/runtime/snapshotBuffer.js +58 -0
  210. package/dist/runtime/transport.d.ts +41 -2
  211. package/dist/runtime/transport.js +7 -1
  212. package/dist/runtime/worldChunks.d.ts +2 -0
  213. package/dist/runtime/worldChunks.js +14 -0
  214. package/dist/runtime/worldProjection.d.ts +9 -0
  215. package/dist/runtime/worldProjection.js +17 -1
  216. package/dist/runtime/worldSnapshot.d.ts +6 -0
  217. package/dist/runtime/worldSnapshot.js +3 -0
  218. package/dist/scene/behaviorRuntime.d.ts +29 -1
  219. package/dist/scene/behaviorRuntime.js +71 -4
  220. package/dist/scene/behaviors.d.ts +12 -1
  221. package/dist/scene/builtinSceneKinds.js +10 -0
  222. package/dist/scene/sceneKinds.d.ts +9 -0
  223. package/dist/scene/vehicleSeat.d.ts +5 -1
  224. package/dist/scene/vehicleSeat.js +5 -1
  225. package/dist/sensor/concealment.d.ts +6 -0
  226. package/dist/sensor/concealment.js +8 -0
  227. package/dist/sensor/freezeMonitor.d.ts +6 -0
  228. package/dist/sensor/freezeMonitor.js +8 -0
  229. package/dist/sensor/perception.d.ts +70 -0
  230. package/dist/sensor/perception.js +189 -0
  231. package/dist/sensor/recordingBuffer.d.ts +3 -0
  232. package/dist/sensor/recordingBuffer.js +8 -0
  233. package/dist/session/roundState.d.ts +4 -0
  234. package/dist/session/roundState.js +11 -0
  235. package/dist/settings/frameRateLimit.d.ts +16 -0
  236. package/dist/settings/frameRateLimit.js +37 -0
  237. package/dist/settings/graphicsProfile.d.ts +21 -0
  238. package/dist/settings/graphicsProfile.js +48 -0
  239. package/dist/settings/graphicsSettings.d.ts +40 -0
  240. package/dist/settings/graphicsSettings.js +81 -0
  241. package/dist/settings/settingsModel.d.ts +9 -1
  242. package/dist/settings/settingsModel.js +10 -0
  243. package/dist/stats/eventMeter.d.ts +1 -1
  244. package/dist/stats/eventMeter.js +1 -1
  245. package/dist/time/accrueSince.d.ts +10 -0
  246. package/dist/time/accrueSince.js +9 -0
  247. package/dist/time/rateWindow.d.ts +16 -0
  248. package/dist/time/rateWindow.js +14 -0
  249. package/dist/time/serverTick.d.ts +21 -3
  250. package/dist/time/serverTick.js +14 -13
  251. package/dist/vfx/feedbackMixer.d.ts +61 -0
  252. package/dist/vfx/feedbackMixer.js +106 -0
  253. package/dist/vfx/particleDirector.d.ts +68 -0
  254. package/dist/vfx/particleDirector.js +81 -0
  255. package/dist/visibility/simulationCulling.d.ts +8 -1
  256. package/dist/visibility/simulationCulling.js +7 -0
  257. package/dist/visibility/spatialIndex.d.ts +21 -0
  258. package/dist/visibility/spatialIndex.js +16 -0
  259. package/dist/world/authoredSolids.d.ts +46 -0
  260. package/dist/world/authoredSolids.js +75 -0
  261. package/dist/world/buildingGenerator.js +2 -2
  262. package/dist/world/buildingKit.d.ts +3 -1
  263. package/dist/world/buildings.d.ts +46 -2
  264. package/dist/world/buildings.js +53 -3
  265. package/dist/world/cityContent.js +103 -25
  266. package/dist/world/cityKind.d.ts +139 -0
  267. package/dist/world/cityKind.js +367 -16
  268. package/dist/world/features.d.ts +56 -3
  269. package/dist/world/features.js +13 -2
  270. package/dist/world/footprintGrid.d.ts +7 -0
  271. package/dist/world/footprintGrid.js +16 -0
  272. package/dist/world/interiors.d.ts +1 -0
  273. package/dist/world/interiors.js +5 -3
  274. package/dist/world/lod.d.ts +6 -0
  275. package/dist/world/lod.js +8 -0
  276. package/dist/world/placement.d.ts +7 -0
  277. package/dist/world/placement.js +2 -0
  278. package/dist/world/placementController.d.ts +1 -1
  279. package/dist/world/placementController.js +7 -0
  280. package/dist/world/raceCircuit.d.ts +168 -0
  281. package/dist/world/raceCircuit.js +797 -0
  282. package/dist/world/terraform.d.ts +8 -0
  283. package/dist/world/terraform.js +7 -0
  284. package/dist/world/terrain.d.ts +5 -1
  285. package/dist/world/terrain.js +5 -0
  286. package/dist/world/territory.d.ts +97 -0
  287. package/dist/world/territory.js +182 -0
  288. package/dist/world/walls.d.ts +7 -0
  289. package/dist/world/walls.js +7 -0
  290. package/dist/world/worldSolids.d.ts +84 -0
  291. package/dist/world/worldSolids.js +240 -0
  292. package/dist/world.d.ts +5 -3
  293. package/dist/world.js +4 -2
  294. package/package.json +1 -1
  295. package/skills/ce-handoff/SKILL.md +2 -2
  296. package/skills/game-design/SKILL.md +11 -1
  297. package/skills/jgengine/SKILL.md +9 -5
  298. package/skills/jgengine/api.md +205 -101
  299. package/skills/jgengine/capabilities.md +56 -0
  300. package/skills/jgengine/recipes/minimal-game.md +3 -0
  301. package/skills/jgengine-combat/SKILL.md +5 -0
  302. package/skills/jgengine-combat/api.md +138 -103
  303. package/skills/jgengine-combat/capabilities.md +29 -1
  304. package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
  305. package/skills/jgengine-gameplay/SKILL.md +10 -0
  306. package/skills/jgengine-gameplay/api.md +501 -453
  307. package/skills/jgengine-gameplay/capabilities.md +36 -0
  308. package/skills/jgengine-gameplay/reference-systems.md +4 -0
  309. package/skills/jgengine-world/SKILL.md +21 -3
  310. package/skills/jgengine-world/api.md +1172 -625
  311. package/skills/jgengine-world/capabilities.md +229 -3
  312. package/skills/jgengine-world/recipes/character-animation.md +39 -0
  313. package/skills/jgengine-world/recipes/controls.md +44 -0
  314. package/skills/jgengine-world/recipes/flight-feel.md +76 -0
  315. package/skills/jgengine-world/recipes/npc-senses.md +43 -0
  316. package/skills/jgengine-world/recipes/vehicle-feel.md +85 -0
  317. package/skills/jgengine-world/reference.md +31 -2
  318. package/skills/jgengine-world/visibility.md +112 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Create the particle intent registry a `GameContext` exposes as `ctx.particles`.
3
+ * State is data-only and serializable: standing emitters survive `snapshot`/`restore`,
4
+ * queued bursts are transient. The renderer subscribes, drains bursts, and mirrors
5
+ * the emitter list — the director never allocates particle pools itself.
6
+ *
7
+ * @capability particle-director game-reachable VFX seam: queue one-shot particle bursts and keyed follow-capable standing emitters as serializable data for the shell to render
8
+ */
9
+ export function createParticleDirector() {
10
+ let nextSeq = 1;
11
+ let bursts = [];
12
+ const emitters = new Map();
13
+ const listeners = new Set();
14
+ function notify() {
15
+ for (const listener of listeners)
16
+ listener();
17
+ }
18
+ return {
19
+ burst(config, count, blending) {
20
+ const wanted = Math.floor(count);
21
+ if (wanted <= 0)
22
+ return;
23
+ bursts.push({ seq: nextSeq++, config, count: wanted, ...(blending === undefined ? {} : { blending }) });
24
+ notify();
25
+ },
26
+ attach(id, config, options) {
27
+ emitters.set(id, {
28
+ id,
29
+ config,
30
+ ...(options?.follow === undefined ? {} : { follow: options.follow }),
31
+ ...(options?.offset === undefined ? {} : { offset: options.offset }),
32
+ ...(options?.blending === undefined ? {} : { blending: options.blending }),
33
+ });
34
+ notify();
35
+ },
36
+ retune(id, patch) {
37
+ const current = emitters.get(id);
38
+ if (current === undefined)
39
+ return;
40
+ emitters.set(id, { ...current, config: { ...current.config, ...patch } });
41
+ notify();
42
+ },
43
+ detach(id) {
44
+ if (!emitters.delete(id))
45
+ return;
46
+ notify();
47
+ },
48
+ clear() {
49
+ if (emitters.size === 0 && bursts.length === 0)
50
+ return;
51
+ emitters.clear();
52
+ bursts = [];
53
+ notify();
54
+ },
55
+ emitters() {
56
+ return Array.from(emitters.values());
57
+ },
58
+ drainBursts() {
59
+ if (bursts.length === 0)
60
+ return [];
61
+ const drained = bursts;
62
+ bursts = [];
63
+ return drained;
64
+ },
65
+ subscribe(listener) {
66
+ listeners.add(listener);
67
+ return () => listeners.delete(listener);
68
+ },
69
+ snapshot() {
70
+ return { nextSeq, emitters: Array.from(emitters.values()) };
71
+ },
72
+ restore(state) {
73
+ nextSeq = state.nextSeq;
74
+ bursts = [];
75
+ emitters.clear();
76
+ for (const spec of state.emitters)
77
+ emitters.set(spec.id, spec);
78
+ notify();
79
+ },
80
+ };
81
+ }
@@ -1,4 +1,4 @@
1
- import type { LodBand } from "../world/lod.js";
1
+ import type { LodBand, LodSchedulerState } from "../world/lod.js";
2
2
  /**
3
3
  * Simulation culling is a SEPARATE, opt-in system from render culling. Render culling only
4
4
  * decides what is drawn; this decides whether a low-priority off-screen entity updates this
@@ -23,6 +23,11 @@ export interface SimulationDecision {
23
23
  /** Seconds of accumulated time delivered when `update` is true (for catch-up integration). */
24
24
  elapsed: number;
25
25
  }
26
+ /** Plain JSON state of a {@link SimulationCuller}: the enabled flag and each id's throttle accumulator. */
27
+ export interface SimulationCullerState {
28
+ enabled: boolean;
29
+ scheduler: LodSchedulerState;
30
+ }
26
31
  export interface SimulationCuller {
27
32
  enabled(): boolean;
28
33
  setEnabled(value: boolean): void;
@@ -30,5 +35,7 @@ export interface SimulationCuller {
30
35
  step(id: string, distance: number, dt: number): SimulationDecision;
31
36
  forget(id: string): void;
32
37
  clear(): void;
38
+ snapshot(): SimulationCullerState;
39
+ restore(next: SimulationCullerState): void;
33
40
  }
34
41
  export declare function createSimulationCuller(options?: SimulationCullingOptions): SimulationCuller;
@@ -32,5 +32,12 @@ export function createSimulationCuller(options = {}) {
32
32
  clear() {
33
33
  scheduler.clear();
34
34
  },
35
+ snapshot() {
36
+ return { enabled, scheduler: scheduler.snapshot() };
37
+ },
38
+ restore(next) {
39
+ enabled = next.enabled;
40
+ scheduler.restore(next.scheduler);
41
+ },
35
42
  };
36
43
  }
@@ -10,6 +10,25 @@ export interface SpatialIndexOptions {
10
10
  */
11
11
  readonly maxCellSpan?: number;
12
12
  }
13
+ /** Integer cell bounds an object covers, inclusive on both ends. */
14
+ export interface SpatialCellRange {
15
+ x0: number;
16
+ y0: number;
17
+ z0: number;
18
+ x1: number;
19
+ y1: number;
20
+ z1: number;
21
+ }
22
+ /**
23
+ * Plain JSON state of a {@link SpatialIndex}. Cells and their ids keep insertion order, so a restored
24
+ * index returns query results in the same order.
25
+ */
26
+ export interface SpatialIndexState {
27
+ /** Per object: its cell range (`null` when oversized) and whether it was inserted as dynamic. */
28
+ entries: [id: string, range: SpatialCellRange | null, dynamic: boolean][];
29
+ cells: [x: number, y: number, z: number, ids: string[]][];
30
+ oversized: string[];
31
+ }
13
32
  /**
14
33
  * A uniform 3D spatial hash the renderer and streaming system query for potentially-visible
15
34
  * objects instead of scanning the whole scene. Objects are keyed by their world AABB into
@@ -39,6 +58,8 @@ export interface SpatialIndex {
39
58
  }[];
40
59
  cellSize(): number;
41
60
  clear(): void;
61
+ snapshot(): SpatialIndexState;
62
+ restore(next: SpatialIndexState): void;
42
63
  }
43
64
  export declare function createSpatialIndex(options?: SpatialIndexOptions): SpatialIndex;
44
65
  /** Precise AABB-vs-AABB test re-exported for callers refining a broad-phase result. */
@@ -206,6 +206,22 @@ export function createSpatialIndex(options = {}) {
206
206
  entries.clear();
207
207
  stamps.clear();
208
208
  },
209
+ snapshot() {
210
+ return {
211
+ entries: [...entries].map(([id, entry]) => [id, entry.range === null ? null : { ...entry.range }, entry.dynamic]),
212
+ cells: [...cells.values()].map((bucket) => [bucket.x, bucket.y, bucket.z, [...bucket.ids]]),
213
+ oversized: [...oversized],
214
+ };
215
+ },
216
+ restore(next) {
217
+ this.clear();
218
+ for (const [id, range, dynamic] of next.entries)
219
+ entries.set(id, { range: range === null ? null : { ...range }, dynamic });
220
+ for (const [x, y, z, ids] of next.cells)
221
+ cells.set(packCell(x, y, z), { x, y, z, ids: new Set(ids) });
222
+ for (const id of next.oversized)
223
+ oversized.add(id);
224
+ },
209
225
  };
210
226
  }
211
227
  /** Precise AABB-vs-AABB test re-exported for callers refining a broad-phase result. */
@@ -0,0 +1,46 @@
1
+ import type { WorldSolid, WorldSolids } from "./worldSolids.js";
2
+ type Vec3Like = {
3
+ x: number;
4
+ y: number;
5
+ z: number;
6
+ };
7
+ /** Minimal editor document shape {@link resolveAuthoredSolids} walks; any `EditorDocument` satisfies it. */
8
+ export interface AuthoredSolidsDocumentLike {
9
+ markers: readonly {
10
+ id: string;
11
+ kind: string;
12
+ position: Vec3Like;
13
+ rotationY?: number;
14
+ meta?: Record<string, unknown>;
15
+ }[];
16
+ volumes: readonly {
17
+ id: string;
18
+ kind: string;
19
+ center: Vec3Like;
20
+ halfExtents?: Vec3Like;
21
+ radius?: number;
22
+ meta?: Record<string, unknown>;
23
+ }[];
24
+ paths: readonly {
25
+ id: string;
26
+ kind: string;
27
+ points: readonly Vec3Like[];
28
+ meta?: Record<string, unknown>;
29
+ }[];
30
+ }
31
+ /** Layer prefix `syncAuthoredSolids` owns in `ctx.world.solids`; one layer per document object. */
32
+ export declare const AUTHORED_SOLID_LAYER_PREFIX = "authored:";
33
+ /**
34
+ * Solids for every document object whose scene kind declares `solids`, keyed by object id. Objects
35
+ * whose kind has no `solids` hook, or whose hook returns none, are left out.
36
+ * @capability authored-solids collision for studio-authored world content such as city volumes
37
+ */
38
+ export declare function resolveAuthoredSolids(document: AuthoredSolidsDocumentLike, sampleHeight?: (x: number, z: number) => number): Map<string, readonly WorldSolid[]>;
39
+ /**
40
+ * Write a document's studio solids into `solids` (usually `ctx.world.solids`), one
41
+ * `authored:<objectId>` layer per object, dropping authored layers the document no longer has.
42
+ * Call again whenever the document changes.
43
+ * @capability authored-solids collision for studio-authored world content such as city volumes
44
+ */
45
+ export declare function syncAuthoredSolids(solids: WorldSolids, document: AuthoredSolidsDocumentLike, sampleHeight?: (x: number, z: number) => number): void;
46
+ export {};
@@ -0,0 +1,75 @@
1
+ import { registerBuiltinSceneKinds } from "../scene/builtinSceneKinds.js";
2
+ import { getSceneKind, parseParams } from "../scene/sceneKinds.js";
3
+ /** Layer prefix `syncAuthoredSolids` owns in `ctx.world.solids`; one layer per document object. */
4
+ export const AUTHORED_SOLID_LAYER_PREFIX = "authored:";
5
+ function kindObjects(document) {
6
+ const objects = [];
7
+ for (const marker of document.markers) {
8
+ objects.push({
9
+ id: marker.id,
10
+ kind: marker.kind,
11
+ position: marker.position,
12
+ ...(marker.rotationY === undefined ? {} : { rotationY: marker.rotationY }),
13
+ ...(marker.meta === undefined ? {} : { meta: marker.meta }),
14
+ });
15
+ }
16
+ for (const volume of document.volumes) {
17
+ objects.push({
18
+ id: volume.id,
19
+ kind: volume.kind,
20
+ center: volume.center,
21
+ ...(volume.halfExtents === undefined ? {} : { halfExtents: volume.halfExtents }),
22
+ ...(volume.radius === undefined ? {} : { radius: volume.radius }),
23
+ ...(volume.meta === undefined ? {} : { meta: volume.meta }),
24
+ });
25
+ }
26
+ for (const path of document.paths) {
27
+ objects.push({
28
+ id: path.id,
29
+ kind: path.kind,
30
+ points: path.points,
31
+ ...(path.meta === undefined ? {} : { meta: path.meta }),
32
+ });
33
+ }
34
+ return objects;
35
+ }
36
+ /**
37
+ * Solids for every document object whose scene kind declares `solids`, keyed by object id. Objects
38
+ * whose kind has no `solids` hook, or whose hook returns none, are left out.
39
+ * @capability authored-solids collision for studio-authored world content such as city volumes
40
+ */
41
+ export function resolveAuthoredSolids(document, sampleHeight) {
42
+ registerBuiltinSceneKinds();
43
+ const out = new Map();
44
+ const objects = kindObjects(document);
45
+ const context = { ...(sampleHeight === undefined ? {} : { sampleHeight }), objects };
46
+ for (const object of objects) {
47
+ const definition = getSceneKind(object.kind);
48
+ if (definition?.solids === undefined || definition.resolve === undefined)
49
+ continue;
50
+ const params = parseParams(definition.schema, object.meta);
51
+ const solids = definition.solids(definition.resolve(object, params, context), object, params, context);
52
+ if (solids.length > 0)
53
+ out.set(object.id, solids);
54
+ }
55
+ return out;
56
+ }
57
+ /**
58
+ * Write a document's studio solids into `solids` (usually `ctx.world.solids`), one
59
+ * `authored:<objectId>` layer per object, dropping authored layers the document no longer has.
60
+ * Call again whenever the document changes.
61
+ * @capability authored-solids collision for studio-authored world content such as city volumes
62
+ */
63
+ export function syncAuthoredSolids(solids, document, sampleHeight) {
64
+ const resolved = resolveAuthoredSolids(document, sampleHeight);
65
+ solids.batch(() => {
66
+ for (const layer of solids.layers()) {
67
+ if (!layer.startsWith(AUTHORED_SOLID_LAYER_PREFIX))
68
+ continue;
69
+ if (!resolved.has(layer.slice(AUTHORED_SOLID_LAYER_PREFIX.length)))
70
+ solids.remove(layer);
71
+ }
72
+ for (const [id, set] of resolved)
73
+ solids.set(`${AUTHORED_SOLID_LAYER_PREFIX}${id}`, set);
74
+ });
75
+ }
@@ -7,7 +7,7 @@
7
7
  * @capability building-generator seeded building as a placeable parametric asset
8
8
  */
9
9
  import { registerAssetGenerator, partsBounds } from "../scene/assetGenerator.js";
10
- import { BUILDING_STYLE_PALETTES, DEFAULT_BUILDING_CONFIG, generateBuilding, resolveBuildingPalette } from "./buildings.js";
10
+ import { BUILDING_STYLE_PALETTES, DEFAULT_BUILDING_CONFIG, buildingSurfaceColor, generateBuilding, resolveBuildingPalette } from "./buildings.js";
11
11
  /** The generator id a catalog entry / placed marker references. */
12
12
  export const BUILDING_GENERATOR_ID = "building";
13
13
  /** The building generator's slider schema — drives the inspector via #809. */
@@ -47,7 +47,7 @@ export function generateBuildingAsset(params, seed) {
47
47
  position: part.position,
48
48
  size: part.scale,
49
49
  rotationY: part.rotationY,
50
- color: palette[part.kind],
50
+ color: buildingSurfaceColor(palette[part.kind]),
51
51
  }));
52
52
  return { parts, bounds: partsBounds(parts) };
53
53
  }
@@ -3,7 +3,7 @@
3
3
  * as an instanced asset kit instead of untextured blocks. Pure serializable data — core resolves
4
4
  * bindings, the renderer loads them.
5
5
  */
6
- import type { BuildingKitSlot, BuildingPartKind, BuildingVariantCounts, Vec3 } from "./buildings.js";
6
+ import type { BuildingKitSlot, BuildingPartKind, BuildingSurfaceMaterial, BuildingVariantCounts, Vec3 } from "./buildings.js";
7
7
  /**
8
8
  * How a kit model's native bounds are mapped onto the slot box `BuildingPartPlacement.scale`
9
9
  * describes.
@@ -32,6 +32,8 @@ export interface BuildingKitPart {
32
32
  /** Multiplier applied after `fit`. */
33
33
  scale?: Vec3;
34
34
  tint?: string;
35
+ /** Tiled PBR maps applied over the model's own materials; `color` tints, `repeat` sets the tiling. */
36
+ material?: BuildingSurfaceMaterial;
35
37
  /**
36
38
  * Quarter-turn the model when its long horizontal axis disagrees with the slot's, so a panel
37
39
  * authored running along Z still tiles a bay that runs along X. Default true — modular kits do not
@@ -103,11 +103,55 @@ export interface BuildingGridConfig {
103
103
  floorRange?: readonly [number, number];
104
104
  }
105
105
  export declare const DEFAULT_BUILDING_CONFIG: BuildingConfig;
106
- export type BuildingPalette = Record<BuildingPartKind, string>;
106
+ /** URLs of the PBR maps a building surface tiles; `buildMaterialCatalog(...).resolve(id)!.maps` fits directly. */
107
+ export interface BuildingSurfaceMaps {
108
+ color?: string;
109
+ normal?: string;
110
+ roughness?: string;
111
+ ao?: string;
112
+ metalness?: string;
113
+ }
114
+ /**
115
+ * A textured building surface: a base colour plus optional tiled PBR maps. With `maps`, `color`
116
+ * multiplies the albedo (leave it light to show the map as authored); without, it is the flat
117
+ * colour the part has always had.
118
+ */
119
+ export interface BuildingSurfaceMaterial {
120
+ /** Base colour; the style's colour for that part kind when unset. */
121
+ color?: string;
122
+ maps?: BuildingSurfaceMaps;
123
+ /**
124
+ * Texture tiles per part slot along [x, y]; a number tiles both. Slots within one building share
125
+ * a size, so one repeat per kind is uniform there; it drifts across buildings with a different
126
+ * `bayWidth`. Default 1.
127
+ */
128
+ repeat?: number | readonly [number, number];
129
+ roughness?: number;
130
+ metalness?: number;
131
+ }
132
+ /** What a building part kind is painted with: a hex colour, or a colour plus tiled PBR maps. */
133
+ export type BuildingSurface = string | BuildingSurfaceMaterial;
134
+ export type BuildingPalette = Record<BuildingPartKind, BuildingSurface>;
107
135
  export type BuildingPaletteOverrides = Partial<BuildingPalette>;
136
+ /** The flat colour of a surface: the hex itself, or a material's `color`, else `fallback`. */
137
+ export declare function buildingSurfaceColor(surface: BuildingSurface | undefined, fallback?: string): string;
138
+ /** Normalises a surface to its material form so a renderer reads one shape. */
139
+ export declare function resolveBuildingSurface(surface: BuildingSurface): BuildingSurfaceMaterial;
108
140
  export type BuildingStyle = "generic" | "capital" | "village" | "desert" | "industrial" | "coastal" | "neon" | "ruin" | "frontier" | "aerial";
109
141
  export declare const DEFAULT_BUILDING_STYLE: BuildingStyle;
110
- export declare const BUILDING_STYLE_PALETTES: Record<BuildingStyle, BuildingPalette>;
142
+ export declare const BUILDING_STYLE_PALETTES: Record<BuildingStyle, Record<BuildingPartKind, string>>;
143
+ /**
144
+ * Per-style facade-tone family: the wall colours a district spreads across its buildings so
145
+ * neighbours differ in hue and value while the block stays one palette. Index 0 is the palette's
146
+ * own `wall`; the rest fan warmer/cooler and lighter/darker around it.
147
+ */
148
+ export declare const BUILDING_STYLE_WALL_TONES: Record<BuildingStyle, readonly string[]>;
149
+ /**
150
+ * The facade-tone family for a style — the ramp {@link BUILDING_STYLE_WALL_TONES} keeps, falling
151
+ * back to the style's single palette wall when a style has no ramp.
152
+ * @internal
153
+ */
154
+ export declare function resolveBuildingWallTones(style?: BuildingStyle): readonly string[];
111
155
  /** @internal */
112
156
  export declare function resolveBuildingPalette(style?: BuildingStyle, overrides?: BuildingPaletteOverrides): BuildingPalette;
113
157
  /**
@@ -38,6 +38,16 @@ export const DEFAULT_BUILDING_CONFIG = {
38
38
  probabilities: DEFAULT_PROBABILITIES,
39
39
  variants: DEFAULT_VARIANTS,
40
40
  };
41
+ /** The flat colour of a surface: the hex itself, or a material's `color`, else `fallback`. */
42
+ export function buildingSurfaceColor(surface, fallback = "#808080") {
43
+ if (surface === undefined)
44
+ return fallback;
45
+ return typeof surface === "string" ? surface : (surface.color ?? fallback);
46
+ }
47
+ /** Normalises a surface to its material form so a renderer reads one shape. */
48
+ export function resolveBuildingSurface(surface) {
49
+ return typeof surface === "string" ? { color: surface } : surface;
50
+ }
41
51
  export const DEFAULT_BUILDING_STYLE = "generic";
42
52
  export const BUILDING_STYLE_PALETTES = {
43
53
  generic: {
@@ -181,6 +191,34 @@ export const BUILDING_STYLE_PALETTES = {
181
191
  corner: "#c3d0d7",
182
192
  },
183
193
  };
194
+ /**
195
+ * Per-style facade-tone family: the wall colours a district spreads across its buildings so
196
+ * neighbours differ in hue and value while the block stays one palette. Index 0 is the palette's
197
+ * own `wall`; the rest fan warmer/cooler and lighter/darker around it.
198
+ */
199
+ export const BUILDING_STYLE_WALL_TONES = {
200
+ generic: ["#9c8f80", "#736a60", "#b7a894", "#8b8a80", "#c3ac90", "#635c54", "#a1978c"],
201
+ capital: ["#cfc5ad", "#ac9878", "#e6e0cf", "#a8a698", "#c8b48d", "#847b68", "#ded2b3"],
202
+ village: ["#b59e7a", "#9c8360", "#c9b895", "#8d7a5f", "#c2a173", "#a8977f", "#d6c3a1"],
203
+ desert: ["#c9a877", "#b08f61", "#ddc396", "#a4855c", "#d3b98d", "#93764f", "#c6b189"],
204
+ industrial: ["#7c838f", "#565d69", "#96a0ac", "#68737a", "#8a8173", "#4a5058", "#a0a6ac"],
205
+ coastal: ["#e8e2d2", "#cfd6d3", "#f1ece0", "#c9c0ac", "#dbe0dc", "#b7b3a3", "#e5d9c4"],
206
+ neon: ["#1b1b26", "#262233", "#141a24", "#2c2030", "#181f2b", "#221d1f", "#101018"],
207
+ ruin: ["#6b6156", "#57503f", "#7d7266", "#4f4a44", "#736450", "#8a8072", "#5d5b53"],
208
+ frontier: ["#7f5539", "#6a462d", "#96684a", "#5c4331", "#8a6540", "#a07a55", "#6f523c"],
209
+ aerial: ["#dfe7ec", "#c6d2da", "#eef3f6", "#b6c3cb", "#d3dde2", "#a7b7c0", "#e7eef1"],
210
+ };
211
+ /**
212
+ * The facade-tone family for a style — the ramp {@link BUILDING_STYLE_WALL_TONES} keeps, falling
213
+ * back to the style's single palette wall when a style has no ramp.
214
+ * @internal
215
+ */
216
+ export function resolveBuildingWallTones(style = DEFAULT_BUILDING_STYLE) {
217
+ const tones = BUILDING_STYLE_WALL_TONES[style];
218
+ if (tones !== undefined && tones.length > 0)
219
+ return tones;
220
+ return [buildingSurfaceColor(resolveBuildingPalette(style).wall)];
221
+ }
184
222
  /** @internal */
185
223
  export function resolveBuildingPalette(style = DEFAULT_BUILDING_STYLE, overrides) {
186
224
  const preset = BUILDING_STYLE_PALETTES[style];
@@ -413,9 +451,21 @@ function pushRoof(parts, config, width, depth) {
413
451
  for (let index = 0; index < roofCells; index += 1) {
414
452
  if (!chance(config.seed, `roof:prop:${index}`, config.probabilities.roofProp))
415
453
  continue;
416
- const x = config.center[0] - width / 2 + config.bayWidth * ((index % config.baysWide) + 0.5);
417
- const z = config.center[1] - depth / 2 + config.bayWidth * (Math.floor(index / config.baysWide) + 0.5);
418
- parts.push(part(config, "roofProp", "roof", [x, height + config.facadeDepth + config.floorHeight * 0.16, z], [config.bayWidth * 0.34, config.floorHeight * 0.32, config.bayWidth * 0.34], `roof:prop:${index}`, { facade: "roof", level: config.floors, bay: index }, ["decor"]));
454
+ // Plant sized and nudged off its cell centre: props locked to the bay grid at one size read as
455
+ // a checkerboard of cubes rather than a roof of mechanical units.
456
+ const wide = hash01(config.seed, `roof:prop:${index}:w`);
457
+ const tall = hash01(config.seed, `roof:prop:${index}:h`);
458
+ const propW = config.bayWidth * (0.2 + wide * 0.4);
459
+ const propD = config.bayWidth * (0.18 + (1 - wide) * 0.36);
460
+ const propH = config.floorHeight * (0.14 + tall * 0.52);
461
+ const x = config.center[0] -
462
+ width / 2 +
463
+ config.bayWidth * ((index % config.baysWide) + 0.5 + (hash01(config.seed, `roof:prop:${index}:x`) - 0.5) * 0.5);
464
+ const z = config.center[1] -
465
+ depth / 2 +
466
+ config.bayWidth *
467
+ (Math.floor(index / config.baysWide) + 0.5 + (hash01(config.seed, `roof:prop:${index}:z`) - 0.5) * 0.5);
468
+ parts.push(part(config, "roofProp", "roof", [x, height + config.facadeDepth + propH / 2, z], [propW, propH, propD], `roof:prop:${index}`, { facade: "roof", level: config.floors, bay: index }, ["decor"]));
419
469
  }
420
470
  }
421
471
  function pushCorners(parts, config, width, depth) {