@jgengine/core 0.11.0 → 0.13.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 (256) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/ai/populationDirector.d.ts +103 -0
  3. package/dist/ai/populationDirector.js +190 -0
  4. package/dist/ai/pursuit.d.ts +56 -0
  5. package/dist/ai/pursuit.js +53 -0
  6. package/dist/authoring.d.ts +1 -1
  7. package/dist/authoring.js +1 -1
  8. package/dist/combat/effects.d.ts +11 -4
  9. package/dist/combat/effects.js +24 -17
  10. package/dist/combat/magazine.d.ts +13 -0
  11. package/dist/combat/magazine.js +27 -0
  12. package/dist/combat/vfxPresets.d.ts +176 -0
  13. package/dist/combat/vfxPresets.js +74 -0
  14. package/dist/combat/weaponFire.d.ts +114 -0
  15. package/dist/combat/weaponFire.js +96 -0
  16. package/dist/combat.d.ts +5 -2
  17. package/dist/combat.js +4 -1
  18. package/dist/devtools/controls.d.ts +38 -0
  19. package/dist/devtools/controls.js +181 -0
  20. package/dist/devtools/devtools.d.ts +5 -197
  21. package/dist/devtools/devtools.js +38 -774
  22. package/dist/devtools/discover.d.ts +32 -0
  23. package/dist/devtools/discover.js +240 -0
  24. package/dist/devtools/fallbackSeams.d.ts +50 -0
  25. package/dist/devtools/fallbackSeams.js +111 -0
  26. package/dist/devtools/frame.d.ts +15 -0
  27. package/dist/devtools/frame.js +247 -0
  28. package/dist/devtools/logs.d.ts +15 -0
  29. package/dist/devtools/logs.js +86 -0
  30. package/dist/devtools/overrides.d.ts +16 -0
  31. package/dist/devtools/overrides.js +66 -0
  32. package/dist/devtools/types.d.ts +212 -0
  33. package/dist/devtools/types.js +1 -0
  34. package/dist/devtools/urlFlags.d.ts +22 -0
  35. package/dist/devtools/urlFlags.js +62 -0
  36. package/dist/economy/auctionBook.d.ts +122 -0
  37. package/dist/economy/auctionBook.js +213 -0
  38. package/dist/economy/priceHistory.d.ts +44 -0
  39. package/dist/economy/priceHistory.js +89 -0
  40. package/dist/economy/unlockPoints.d.ts +92 -0
  41. package/dist/economy/unlockPoints.js +103 -0
  42. package/dist/editor/commandApply.d.ts +5 -0
  43. package/dist/editor/commandApply.js +755 -0
  44. package/dist/editor/commands.d.ts +48 -3
  45. package/dist/editor/commands.js +40 -754
  46. package/dist/editor/directives.d.ts +94 -0
  47. package/dist/editor/directives.js +206 -0
  48. package/dist/editor/document.d.ts +37 -2
  49. package/dist/editor/document.js +422 -6
  50. package/dist/editor/entityCatalog.d.ts +42 -0
  51. package/dist/editor/entityCatalog.js +101 -0
  52. package/dist/editor/environment.d.ts +74 -0
  53. package/dist/editor/environment.js +169 -0
  54. package/dist/editor/index.d.ts +26 -1
  55. package/dist/editor/index.js +8 -1
  56. package/dist/editor/minimap.d.ts +31 -0
  57. package/dist/editor/minimap.js +73 -0
  58. package/dist/editor/streamer.d.ts +66 -0
  59. package/dist/editor/streamer.js +68 -0
  60. package/dist/editor/types.d.ts +143 -0
  61. package/dist/editor/world.d.ts +136 -0
  62. package/dist/editor/world.js +303 -0
  63. package/dist/game/breeding.d.ts +161 -0
  64. package/dist/game/breeding.js +181 -0
  65. package/dist/game/cameraConfig.d.ts +451 -0
  66. package/dist/game/cameraConfig.js +1 -0
  67. package/dist/game/clipRoles.d.ts +51 -0
  68. package/dist/game/clipRoles.js +142 -0
  69. package/dist/game/defineGame.d.ts +16 -9
  70. package/dist/game/defineGame.js +14 -6
  71. package/dist/game/defineSystem.d.ts +2 -0
  72. package/dist/game/defineSystem.js +2 -0
  73. package/dist/game/events.d.ts +28 -0
  74. package/dist/game/partAnimation.d.ts +78 -0
  75. package/dist/game/partAnimation.js +147 -0
  76. package/dist/game/playableGame.d.ts +72 -440
  77. package/dist/game/playableGame.js +1 -1
  78. package/dist/game/progression.d.ts +35 -0
  79. package/dist/game/progression.js +22 -1
  80. package/dist/game/quest.js +1 -3
  81. package/dist/game/tribe.d.ts +184 -0
  82. package/dist/game/tribe.js +432 -0
  83. package/dist/gameplay.d.ts +3 -1
  84. package/dist/gameplay.js +3 -1
  85. package/dist/inventory/encumbrance.d.ts +69 -0
  86. package/dist/inventory/encumbrance.js +68 -0
  87. package/dist/inventory/inventoryModel.d.ts +24 -8
  88. package/dist/inventory/inventoryModel.js +47 -8
  89. package/dist/item/durability.js +1 -3
  90. package/dist/meta/changelog.d.ts +1 -1
  91. package/dist/meta/changelog.js +48 -1
  92. package/dist/movement/movementModel.d.ts +15 -1
  93. package/dist/movement/movementModel.js +33 -15
  94. package/dist/movement/playerMovement.js +102 -2
  95. package/dist/multiplayer/combatSnapshot.js +13 -8
  96. package/dist/multiplayer/presenceModel.d.ts +2 -0
  97. package/dist/multiplayer/presenceModel.js +8 -0
  98. package/dist/physics/drivableVehicle.d.ts +2 -0
  99. package/dist/physics/drivableVehicle.js +1 -1
  100. package/dist/physics/flightDynamics.d.ts +82 -0
  101. package/dist/physics/flightDynamics.js +165 -0
  102. package/dist/physics/gravityField.d.ts +25 -0
  103. package/dist/physics/gravityField.js +41 -0
  104. package/dist/physics/kinematicVehicle.d.ts +87 -0
  105. package/dist/physics/kinematicVehicle.js +193 -11
  106. package/dist/physics/physicsWorld.d.ts +26 -0
  107. package/dist/physics/physicsWorld.js +109 -117
  108. package/dist/physics/vehicleObstacles.d.ts +44 -0
  109. package/dist/physics/vehicleObstacles.js +115 -0
  110. package/dist/random/rng.d.ts +4 -1
  111. package/dist/random/rng.js +4 -1
  112. package/dist/runtime/adapter.d.ts +13 -0
  113. package/dist/runtime/adapter.js +13 -0
  114. package/dist/runtime/context/combat.d.ts +51 -0
  115. package/dist/runtime/context/combat.js +131 -0
  116. package/dist/runtime/context/combatFx.js +15 -5
  117. package/dist/runtime/context/deathLoot.d.ts +30 -0
  118. package/dist/runtime/context/deathLoot.js +39 -0
  119. package/dist/runtime/context/player.d.ts +48 -0
  120. package/dist/runtime/context/player.js +125 -0
  121. package/dist/runtime/context/scene.d.ts +60 -0
  122. package/dist/runtime/context/scene.js +329 -0
  123. package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
  124. package/dist/runtime/context/snapshotCodecs.js +26 -0
  125. package/dist/runtime/descriptors/baseline.js +26 -3
  126. package/dist/runtime/descriptors/features.d.ts +5 -0
  127. package/dist/runtime/descriptors/features.js +20 -2
  128. package/dist/runtime/gameContext.d.ts +2 -428
  129. package/dist/runtime/gameContext.js +133 -500
  130. package/dist/runtime/gameContextTypes.d.ts +500 -0
  131. package/dist/runtime/gameContextTypes.js +1 -0
  132. package/dist/runtime/headlessRunner.d.ts +17 -2
  133. package/dist/runtime/headlessRunner.js +15 -0
  134. package/dist/runtime/hostPolicy.d.ts +51 -0
  135. package/dist/runtime/hostPolicy.js +68 -0
  136. package/dist/runtime/hostedGameRunner.d.ts +3 -1
  137. package/dist/runtime/hostedGameRunner.js +1 -0
  138. package/dist/runtime/hostedWorldSession.d.ts +3 -1
  139. package/dist/runtime/hostedWorldSession.js +1 -0
  140. package/dist/runtime/worldSnapshot.d.ts +13 -1
  141. package/dist/runtime/worldSnapshot.js +15 -3
  142. package/dist/scene/assetCatalog.d.ts +7 -0
  143. package/dist/scene/assetSpace.js +1 -3
  144. package/dist/scene/authoredTriggers.d.ts +50 -0
  145. package/dist/scene/authoredTriggers.js +114 -0
  146. package/dist/scene/builtinSceneKinds.js +8 -3
  147. package/dist/scene/colliders.d.ts +79 -0
  148. package/dist/scene/colliders.js +170 -0
  149. package/dist/scene/collisionMesh.d.ts +94 -0
  150. package/dist/scene/collisionMesh.js +699 -0
  151. package/dist/scene/companion.d.ts +147 -0
  152. package/dist/scene/companion.js +193 -0
  153. package/dist/scene/entityStats.d.ts +7 -9
  154. package/dist/scene/entityStats.js +19 -15
  155. package/dist/scene/sceneKinds.d.ts +16 -0
  156. package/dist/scene/sceneKinds.js +4 -0
  157. package/dist/scene/sceneRaycast.js +15 -0
  158. package/dist/stats/spawnLevelStats.d.ts +110 -0
  159. package/dist/stats/spawnLevelStats.js +140 -0
  160. package/dist/stats/statModifiers.d.ts +16 -0
  161. package/dist/stats/statModifiers.js +30 -1
  162. package/dist/stats/statPool.d.ts +70 -0
  163. package/dist/stats/statPool.js +62 -0
  164. package/dist/store/changeSignal.d.ts +3 -0
  165. package/dist/store/changeSignal.js +2 -0
  166. package/dist/store/observableKeyedStore.d.ts +2 -0
  167. package/dist/store/observableKeyedStore.js +1 -0
  168. package/dist/survival/decayMeter.js +1 -3
  169. package/dist/survival/regionHealth.js +1 -3
  170. package/dist/ui/hudLayout.d.ts +5 -0
  171. package/dist/ui/hudLayout.js +17 -1
  172. package/dist/ui/panelModel.d.ts +120 -0
  173. package/dist/ui/panelModel.js +168 -0
  174. package/dist/ui.d.ts +1 -0
  175. package/dist/ui.js +1 -0
  176. package/dist/world/authoredEntities.d.ts +29 -0
  177. package/dist/world/authoredEntities.js +29 -0
  178. package/dist/world/authoredObjects.d.ts +15 -1
  179. package/dist/world/authoredObjects.js +13 -1
  180. package/dist/world/authoredSpawn.d.ts +39 -0
  181. package/dist/world/authoredSpawn.js +37 -0
  182. package/dist/world/buildSockets.d.ts +152 -0
  183. package/dist/world/buildSockets.js +188 -0
  184. package/dist/world/buildingLots.d.ts +84 -0
  185. package/dist/world/buildingLots.js +119 -0
  186. package/dist/world/buildings.d.ts +6 -0
  187. package/dist/world/cityBlocks.d.ts +152 -0
  188. package/dist/world/cityBlocks.js +670 -0
  189. package/dist/world/cityContent.d.ts +77 -0
  190. package/dist/world/cityContent.js +261 -0
  191. package/dist/world/cityGenerator.d.ts +41 -0
  192. package/dist/world/cityGenerator.js +58 -0
  193. package/dist/world/cityGeometry.d.ts +114 -0
  194. package/dist/world/cityGeometry.js +573 -0
  195. package/dist/world/cityKind.d.ts +314 -0
  196. package/dist/world/cityKind.js +1578 -0
  197. package/dist/world/environmentSummary.js +57 -1
  198. package/dist/world/features.d.ts +53 -5
  199. package/dist/world/features.js +15 -3
  200. package/dist/world/grassKind.js +2 -2
  201. package/dist/world/markers.d.ts +50 -4
  202. package/dist/world/markers.js +49 -0
  203. package/dist/world/massing.js +1 -1
  204. package/dist/world/minimapBake.d.ts +78 -0
  205. package/dist/world/minimapBake.js +208 -0
  206. package/dist/world/minimapTrack.d.ts +17 -0
  207. package/dist/world/minimapTrack.js +22 -0
  208. package/dist/world/place.d.ts +166 -0
  209. package/dist/world/place.js +96 -0
  210. package/dist/world/placeAsset.js +3 -1
  211. package/dist/world/poleLineKind.d.ts +40 -0
  212. package/dist/world/poleLineKind.js +97 -0
  213. package/dist/world/polyline.js +9 -2
  214. package/dist/world/resourceNode.d.ts +109 -0
  215. package/dist/world/resourceNode.js +140 -0
  216. package/dist/world/roads.d.ts +19 -2
  217. package/dist/world/roads.js +60 -3
  218. package/dist/world/scatter.js +2 -20
  219. package/dist/world/streetGenerator.d.ts +159 -0
  220. package/dist/world/streetGenerator.js +684 -0
  221. package/dist/world/streets.d.ts +103 -1
  222. package/dist/world/streets.js +273 -0
  223. package/dist/world/terraform.js +58 -88
  224. package/dist/world/terrain.d.ts +65 -1
  225. package/dist/world/terrain.js +119 -2
  226. package/dist/world.d.ts +14 -5
  227. package/dist/world.js +12 -3
  228. package/package.json +1 -1
  229. package/skills/game-design/SKILL.md +28 -0
  230. package/skills/game-design/agents/openai.yaml +4 -0
  231. package/skills/game-design/references/application-playbook.md +170 -0
  232. package/skills/game-design/references/field-guide.md +288 -0
  233. package/skills/jgengine/SKILL.md +8 -4
  234. package/skills/jgengine/api.md +182 -81
  235. package/skills/jgengine/capabilities.md +104 -2
  236. package/skills/jgengine/recipes/minimal-game.md +88 -0
  237. package/skills/jgengine-combat/SKILL.md +19 -1
  238. package/skills/jgengine-combat/api.md +75 -2
  239. package/skills/jgengine-combat/capabilities.md +61 -1
  240. package/skills/jgengine-combat/recipes/portable-damage-effects.md +197 -0
  241. package/skills/jgengine-combat/recipes/portable-runtime-state.md +154 -0
  242. package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
  243. package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +156 -0
  244. package/skills/jgengine-combat/recipes/visible-attacks.md +81 -0
  245. package/skills/jgengine-combat/reference.md +33 -4
  246. package/skills/jgengine-gameplay/SKILL.md +8 -0
  247. package/skills/jgengine-gameplay/api.md +182 -26
  248. package/skills/jgengine-gameplay/capabilities.md +96 -2
  249. package/skills/jgengine-gameplay/recipes/portable-xp-leveling.md +168 -0
  250. package/skills/jgengine-gameplay/reference-systems.md +10 -5
  251. package/skills/jgengine-world/SKILL.md +13 -0
  252. package/skills/jgengine-world/api.md +453 -21
  253. package/skills/jgengine-world/capabilities.md +112 -2
  254. package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
  255. package/skills/jgengine-world/reference.md +43 -4
  256. package/skills/jgengine/reference-cartridge.md +0 -72
@@ -1,7 +1,11 @@
1
- import { generateBuildingDistrict, resolveBuildingPalette, } from "./buildings.js";
1
+ import { DEFAULT_BUILDING_CONFIG, generateBuilding, generateBuildingDistrict, resolveBuildingPalette, } from "./buildings.js";
2
+ import { deriveBuildingLots } from "./buildingLots.js";
2
3
  import { pathLength } from "./roads.js";
3
4
  import { resolveTerrainField, resolveTerrainPalette } from "./terrain.js";
4
5
  export function resolveStructureBuildings(descriptor) {
6
+ if (descriptor.along !== undefined) {
7
+ return resolveFrontageBuildings(descriptor, descriptor.along);
8
+ }
5
9
  const columns = Math.max(1, Math.ceil(Math.sqrt(descriptor.count)));
6
10
  const rows = Math.max(1, Math.ceil(descriptor.count / columns));
7
11
  const spacing = descriptor.spacing;
@@ -21,6 +25,58 @@ export function resolveStructureBuildings(descriptor) {
21
25
  ...(descriptor.seed === undefined ? {} : { seed: descriptor.seed }),
22
26
  }).slice(0, descriptor.count);
23
27
  }
28
+ function hashU32(input) {
29
+ let hash = 2166136261;
30
+ for (let i = 0; i < input.length; i += 1) {
31
+ hash ^= input.charCodeAt(i);
32
+ hash = Math.imul(hash, 16777619);
33
+ }
34
+ hash ^= hash >>> 16;
35
+ hash = Math.imul(hash, 2246822507);
36
+ hash ^= hash >>> 13;
37
+ hash = Math.imul(hash, 3266489909);
38
+ hash ^= hash >>> 16;
39
+ return hash >>> 0;
40
+ }
41
+ /**
42
+ * Build one street-aware building per road-frontage lot: massed at the lot center, its `rotationY`
43
+ * set so the front faces the road (the renderer turns the whole building about `center`), floors
44
+ * rolled deterministically from the descriptor's `stories` and the lot's road/side/index.
45
+ */
46
+ function resolveFrontageBuildings(descriptor, along) {
47
+ const seed = descriptor.seed ?? "buildings";
48
+ const lots = deriveBuildingLots({
49
+ roads: along.roads,
50
+ footprint: descriptor.footprint,
51
+ spacing: descriptor.spacing,
52
+ maxLots: descriptor.count,
53
+ seed: `${seed}:lots`,
54
+ ...(along.setback === undefined ? {} : { setback: along.setback }),
55
+ ...(along.bothSides === undefined ? {} : { bothSides: along.bothSides }),
56
+ ...(along.area === undefined ? {} : { area: along.area }),
57
+ });
58
+ const [floorsMin, floorsMax] = descriptor.stories;
59
+ const bayWidth = DEFAULT_BUILDING_CONFIG.bayWidth;
60
+ return lots.map((lot, index) => buildLot(lot, index, seed, floorsMin, floorsMax, bayWidth, descriptor.storyHeight));
61
+ }
62
+ function buildLot(lot, index, seed, floorsMin, floorsMax, bayWidth, storyHeight) {
63
+ const lo = Math.min(floorsMin, floorsMax);
64
+ const hi = Math.max(floorsMin, floorsMax);
65
+ const floors = lo + Math.floor((hashU32(`${seed}:floors:${index}`) / 4294967296) * (hi - lo + 1));
66
+ const baysWide = Math.max(1, Math.floor(lot.footprint.w / bayWidth));
67
+ const baysDeep = Math.max(1, Math.floor(lot.footprint.d / bayWidth));
68
+ const building = generateBuilding({
69
+ id: `frontage-${index}`,
70
+ seed: `${seed}:${index}`,
71
+ center: lot.center,
72
+ floors,
73
+ baysWide,
74
+ baysDeep,
75
+ bayWidth,
76
+ floorHeight: storyHeight,
77
+ });
78
+ return { ...building, rotationY: lot.rotationY };
79
+ }
24
80
  const TERRAIN_SAMPLE_RESOLUTION = 16;
25
81
  function sampleTerrainHeights(descriptor) {
26
82
  const field = resolveTerrainField(descriptor);
@@ -1,6 +1,7 @@
1
1
  import type { BuildingPaletteOverrides, BuildingStyle } from "./buildings.js";
2
2
  import type { AvoidZone } from "./geometry.js";
3
3
  import type { TerrainPathProfile } from "./pathTerrain.js";
4
+ import type { PlaceWorldFeature } from "./place.js";
4
5
  import type { TerraformSnapshot } from "./terraform.js";
5
6
  import type { VolumetricCloudsConfig } from "./volumetricClouds.js";
6
7
  export interface WorldBounds {
@@ -290,6 +291,34 @@ export interface SkyEnvironmentConfig {
290
291
  /** Raymarched volumetric cloud layer over the sky dome. Off by default — omit for no clouds. */
291
292
  volumetricClouds?: VolumetricCloudsConfig;
292
293
  }
294
+ /** One road a `building({ along })` frontage lines with buildings: centerline + full width. */
295
+ export interface BuildingFrontageRoad {
296
+ /** Road centerline vertices in world XZ; at least two points. */
297
+ path: readonly EnvironmentVec2[];
298
+ /** Road full width in world units; lots set back from `width / 2`. */
299
+ width: number;
300
+ }
301
+ /**
302
+ * Street-aware placement for `building()`: instead of a grid scattered around `position`, lots are
303
+ * stepped along each road's frontage, offset by a consistent setback (a curb + sidewalk strip), and
304
+ * turned so every building FRONT faces its road. `footprint` (`w` = frontage width, `d` = depth) and
305
+ * `spacing` (along-road gap) are shared with the grid mode. Deterministic and bounded by `maxLots`.
306
+ */
307
+ export interface BuildingFrontageConfig {
308
+ /** Roads whose frontage is lined with lots. */
309
+ roads: readonly BuildingFrontageRoad[];
310
+ /** Sidewalk strip between the curb (`width / 2`) and the building front, world units. Default 3. */
311
+ setback?: number;
312
+ /** Place lots on both sides of each road. Default true. */
313
+ bothSides?: boolean;
314
+ /** Clip region; lots whose center falls outside are dropped. Omit to keep every lot. */
315
+ area?: {
316
+ center?: EnvironmentVec2;
317
+ halfExtents: EnvironmentVec2;
318
+ };
319
+ /** Hard cap on emitted lots so a long network stays bounded. Default 400. */
320
+ maxLots?: number;
321
+ }
293
322
  export interface BuildingEnvironmentConfig {
294
323
  count?: number;
295
324
  position?: EnvironmentVec2;
@@ -302,6 +331,11 @@ export interface BuildingEnvironmentConfig {
302
331
  /** Explicit per-part-kind hex colors; any part left unset falls back to the resolved `style` palette. */
303
332
  palette?: BuildingPaletteOverrides;
304
333
  seed?: string;
334
+ /**
335
+ * Street-aware placement: derive lots along the given roads' frontage (facing each street, set
336
+ * back by a sidewalk strip) instead of the grid around `position`. See {@link BuildingFrontageConfig}.
337
+ */
338
+ along?: BuildingFrontageConfig;
305
339
  }
306
340
  export type PadSize = readonly [number, number] | {
307
341
  radius: number;
@@ -343,7 +377,7 @@ export type OceanEnvironmentDescriptor = {
343
377
  } & Required<Pick<OceanEnvironmentConfig, "bounds" | "level" | "waveHeight" | "waveScale" | "waveSpeed" | "color">> & Pick<OceanEnvironmentConfig, "position" | "levelAt">;
344
378
  export type BuildingEnvironmentDescriptor = {
345
379
  kind: "building";
346
- } & Required<Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style">> & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette">;
380
+ } & Required<Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style">> & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette" | "along">;
347
381
  export type PadEnvironmentDescriptor = {
348
382
  kind: "pad";
349
383
  } & Required<Pick<PadEnvironmentConfig, "center" | "size" | "height" | "color">> & Pick<PadEnvironmentConfig, "rotationY" | "elevation">;
@@ -464,8 +498,13 @@ export interface PlotsWorldConfig extends WorldGridConfig {
464
498
  export interface TilemapWorldConfig extends WorldGridConfig {
465
499
  map: string;
466
500
  }
467
- /** A declared world shape — biomes, voxel grid, plots, tilemap, environment, or flat — passed to `defineGame`. */
468
- export type WorldFeature = ({
501
+ /**
502
+ * A declared world shape passed to `defineGame`. The preferred model is the place feature from
503
+ * `world()` (`@jgengine/core/world/place`): substrate + laws, with all dressing authored in the
504
+ * editor. The remaining members — biomes, voxel grid, plots, tilemap, environment, flat — are the
505
+ * legacy code-declared shapes kept for existing games.
506
+ */
507
+ export type WorldFeature = PlaceWorldFeature | ({
469
508
  kind: "biomes";
470
509
  } & BiomesWorldConfig) | ({
471
510
  kind: "voxel";
@@ -478,7 +517,16 @@ export type WorldFeature = ({
478
517
  };
479
518
  /** Derives implicit `TerrainFlattenMask`s carving each pad's footprint into the terrain beneath it. Elevated pads (absolute `elevation`) float free and carve nothing. */
480
519
  export declare function padFlattenMasks(pads: readonly PadEnvironmentDescriptor[]): readonly TerrainFlattenMask[];
481
- /** Composes an `environment()` world feature from terrain, sky, weather, vegetation, water, structures, roads, and pads. */
520
+ /** Composes an `environment()` feature from terrain, sky, weather, vegetation, water, structures, roads, and pads.
521
+ *
522
+ * Legacy dressing-in-code path — not how a world is defined. A world is the place you play in
523
+ * (`world()` from `world/place`: ground + laws); sky look, foliage scatter, props, and sculpt are
524
+ * scene content authored in the editor. Reach for `environment()` only when consuming
525
+ * editor/preset-written scene data (`environmentContentFromDocument`) or maintaining an existing
526
+ * game; never scaffold new worlds from it.
527
+ *
528
+ * @capability world-environment compose editor/preset-derived outdoor dressing — terrain, weather, water, structures — into a legacy environment feature
529
+ */
482
530
  export declare function environment(config?: EnvironmentWorldConfig): EnvironmentWorldFeature;
483
531
  /** Declares a heightfield terrain patch for `environment()` — bounds, noise, materials, and flatten masks. */
484
532
  export declare function terrain(config?: TerrainEnvironmentConfig): TerrainEnvironmentDescriptor;
@@ -494,7 +542,7 @@ export declare function ocean(config?: OceanEnvironmentConfig): OceanEnvironment
494
542
  /** Declare a road ribbon for an `environment()` world; the shell drapes and renders it over the terrain. */
495
543
  export declare function road(config: RoadEnvironmentConfig): RoadEnvironmentDescriptor;
496
544
  export declare function island(config: TerrainIslandConfig): TerrainIslandDescriptor;
497
- /** Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style. */
545
+ /** Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style. Pass `along` to line road frontage instead of gridding around `position`. */
498
546
  export declare function building(config?: BuildingEnvironmentConfig): BuildingEnvironmentDescriptor;
499
547
  export declare function pad(config: PadEnvironmentConfig): PadEnvironmentDescriptor;
500
548
  /** Declares a biome-painted world — the whole-world alternative to a single `environment()` terrain. */
@@ -43,7 +43,16 @@ function bandWeatherDescriptors(terrainDescriptor) {
43
43
  }
44
44
  return out;
45
45
  }
46
- /** Composes an `environment()` world feature from terrain, sky, weather, vegetation, water, structures, roads, and pads. */
46
+ /** Composes an `environment()` feature from terrain, sky, weather, vegetation, water, structures, roads, and pads.
47
+ *
48
+ * Legacy dressing-in-code path — not how a world is defined. A world is the place you play in
49
+ * (`world()` from `world/place`: ground + laws); sky look, foliage scatter, props, and sculpt are
50
+ * scene content authored in the editor. Reach for `environment()` only when consuming
51
+ * editor/preset-written scene data (`environmentContentFromDocument`) or maintaining an existing
52
+ * game; never scaffold new worlds from it.
53
+ *
54
+ * @capability world-environment compose editor/preset-derived outdoor dressing — terrain, weather, water, structures — into a legacy environment feature
55
+ */
47
56
  export function environment(config = {}) {
48
57
  const explicitWeather = list(config.weather);
49
58
  const bandWeather = bandWeatherDescriptors(config.terrain);
@@ -219,11 +228,13 @@ export function island(config) {
219
228
  const { kind: _kind, ...descriptor } = terrain(terrainConfig);
220
229
  return { ...descriptor, kind: "island", origin };
221
230
  }
222
- /** Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style. */
231
+ /** Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style. Pass `along` to line road frontage instead of gridding around `position`. */
223
232
  export function building(config = {}) {
224
233
  return withOptional({
225
234
  kind: "building",
226
- count: config.count ?? 1,
235
+ // In frontage mode the road network sizes the district; `count` becomes an upper bound, so a
236
+ // caller that just says `along` gets a filled street rather than a single grid building.
237
+ count: config.count ?? (config.along === undefined ? 1 : config.along.maxLots ?? 400),
227
238
  footprint: config.footprint ?? { w: 8, d: 8 },
228
239
  stories: config.stories ?? [1, 4],
229
240
  storyHeight: config.storyHeight ?? 3,
@@ -233,6 +244,7 @@ export function building(config = {}) {
233
244
  ...(config.seed === undefined ? {} : { seed: config.seed }),
234
245
  ...(config.position === undefined ? {} : { position: config.position }),
235
246
  ...(config.palette === undefined ? {} : { palette: config.palette }),
247
+ ...(config.along === undefined ? {} : { along: config.along }),
236
248
  });
237
249
  }
238
250
  export function pad(config) {
@@ -12,7 +12,7 @@ import { registerSceneKind } from "../scene/sceneKinds.js";
12
12
  export const GRASS_FIELD_KIND = "grass_field";
13
13
  /** Grass defaults: a lush, lightly-swaying meadow. */
14
14
  export const GRASS_DEFAULTS = {
15
- density: 4,
15
+ density: 12,
16
16
  bladeHeight: 0.6,
17
17
  colorBase: "#3f6f37",
18
18
  colorTip: "#9ac25a",
@@ -26,7 +26,7 @@ export const GRASS_DEFAULTS = {
26
26
  /** The grass parameter schema — drives the inspector and `meta` parse via the studio seam. */
27
27
  export const GRASS_SCHEMA = {
28
28
  fields: [
29
- { type: "range", key: "density", label: "density", min: 0.5, max: 16, step: 0.5, default: GRASS_DEFAULTS.density, unit: "/m²" },
29
+ { type: "range", key: "density", label: "density", min: 0.5, max: 160, step: 0.5, default: GRASS_DEFAULTS.density, unit: "/m²" },
30
30
  { type: "range", key: "bladeHeight", label: "blade height", min: 0.1, max: 2, step: 0.05, default: GRASS_DEFAULTS.bladeHeight, unit: "m" },
31
31
  { type: "color", key: "colorBase", label: "base color", default: GRASS_DEFAULTS.colorBase },
32
32
  { type: "color", key: "colorTip", label: "tip color", default: GRASS_DEFAULTS.colorTip },
@@ -1,19 +1,32 @@
1
1
  export type MarkerPosition = readonly [number, number, number];
2
- export interface MapMarker<TMeta = unknown> {
2
+ /**
3
+ * Small, display-only marker shape consumed by map renderers. Existing games
4
+ * can project their own entities to this view without adopting marker
5
+ * lifecycle storage or duplicating them into a {@link MarkerSet}.
6
+ */
7
+ export interface MarkerView<TMeta = unknown> {
3
8
  id: string;
4
- kind: string;
5
9
  position: MarkerPosition;
10
+ kind?: string;
6
11
  label?: string;
12
+ /** Optional world heading in radians for presentations that draw directional markers. */
13
+ heading?: number;
14
+ meta?: TMeta;
15
+ }
16
+ /** A marker owned by {@link MarkerSet}, including its lifecycle and query fields. */
17
+ export interface MapMarker<TMeta = unknown> extends MarkerView<TMeta> {
18
+ kind: string;
7
19
  owner?: string;
8
20
  createdAt: number;
9
21
  expiresAt?: number;
10
- meta?: TMeta;
11
22
  }
12
23
  export interface MarkerInput<TMeta = unknown> {
13
24
  id?: string;
14
25
  kind: string;
15
26
  position: MarkerPosition;
16
27
  label?: string;
28
+ /** Optional world heading in radians for directional map presentations. */
29
+ heading?: number;
17
30
  owner?: string;
18
31
  createdAt?: number;
19
32
  expiresAt?: number;
@@ -37,7 +50,7 @@ export interface MarkerKindStyle {
37
50
  priority: number;
38
51
  }
39
52
  export declare const DEFAULT_MARKER_KINDS: Record<string, MarkerKindStyle>;
40
- export declare function markerKindStyle(kind: string, styles?: Record<string, MarkerKindStyle>): MarkerKindStyle;
53
+ export declare function markerKindStyle(kind: string | undefined, styles?: Record<string, MarkerKindStyle>): MarkerKindStyle;
41
54
  export interface MarkerSet<TMeta = unknown> {
42
55
  add(marker: MarkerInput<TMeta>): string;
43
56
  remove(id: string): boolean;
@@ -49,4 +62,37 @@ export interface MarkerSet<TMeta = unknown> {
49
62
  subscribe(listener: () => void): () => void;
50
63
  snapshot(): readonly MapMarker<TMeta>[];
51
64
  }
65
+ /**
66
+ * Observable marker snapshots owned by an external project. `getSnapshot`
67
+ * must return the same array identity until the source changes and calls its
68
+ * subscribers, matching React's external-store contract.
69
+ */
70
+ export interface MarkerSource<TMarker extends MarkerView = MarkerView> {
71
+ subscribe(listener: () => void): () => void;
72
+ getSnapshot(): readonly TMarker[];
73
+ /** Optional hydration snapshot; omit when the client snapshot is also valid during SSR. */
74
+ getServerSnapshot?: () => readonly TMarker[];
75
+ }
76
+ /** Marker data accepted by portable consumers: static views, an external source, or a native set. */
77
+ export type MarkerCollection<TMarker extends MarkerView = MarkerView> = readonly TMarker[] | MarkerSource<TMarker> | MarkerSet;
78
+ /** Configuration for projecting a caller-owned collection into display-only markers. */
79
+ export interface MarkerSourceOptions<TEntity, TMarker extends MarkerView = MarkerView> {
80
+ /** Subscribe to changes in the caller-owned collection. Static collections may omit this. */
81
+ subscribe?: (listener: () => void) => () => void;
82
+ /** Read the current caller-owned collection. Keep its identity stable between changes. */
83
+ getSnapshot: () => readonly TEntity[];
84
+ /** Optional server collection used to keep hydration output deterministic. */
85
+ getServerSnapshot?: () => readonly TEntity[];
86
+ /** Project one caller-owned record into the minimal marker view. */
87
+ project: (value: TEntity, index: number) => TMarker;
88
+ }
89
+ /**
90
+ * Adapt a caller-owned array/store to an observable marker source. Projection
91
+ * is cached between source changes, so React reads do not copy the collection
92
+ * per frame. The caller retains ownership of entities, updates, persistence,
93
+ * and subscription scheduling.
94
+ *
95
+ * @capability portable-marker-source project caller-owned arrays or subscribable stores into cached minimap marker snapshots without a MarkerSet
96
+ */
97
+ export declare function createMarkerSource<TEntity, TMarker extends MarkerView = MarkerView>(options: MarkerSourceOptions<TEntity, TMarker>): MarkerSource<TMarker>;
52
98
  export declare function createMarkerSet<TMeta = unknown>(now?: () => number): MarkerSet<TMeta>;
@@ -10,8 +10,55 @@ export const DEFAULT_MARKER_KINDS = {
10
10
  ping: { id: "ping", color: "#22d3ee", glyph: "◎", priority: 65 },
11
11
  };
12
12
  export function markerKindStyle(kind, styles = DEFAULT_MARKER_KINDS) {
13
+ if (kind === undefined)
14
+ return { id: "marker", color: "#e2e8f0", glyph: "\u2022", priority: 30 };
13
15
  return styles[kind] ?? { id: kind, color: "#e2e8f0", glyph: "•", priority: 30 };
14
16
  }
17
+ function projectedSnapshot(readSource, project) {
18
+ let dirty = true;
19
+ let source;
20
+ let snapshot = [];
21
+ return {
22
+ invalidate() {
23
+ dirty = true;
24
+ },
25
+ read() {
26
+ const next = readSource();
27
+ if (dirty || next !== source) {
28
+ source = next;
29
+ snapshot = next.map(project);
30
+ dirty = false;
31
+ }
32
+ return snapshot;
33
+ },
34
+ };
35
+ }
36
+ /**
37
+ * Adapt a caller-owned array/store to an observable marker source. Projection
38
+ * is cached between source changes, so React reads do not copy the collection
39
+ * per frame. The caller retains ownership of entities, updates, persistence,
40
+ * and subscription scheduling.
41
+ *
42
+ * @capability portable-marker-source project caller-owned arrays or subscribable stores into cached minimap marker snapshots without a MarkerSet
43
+ */
44
+ export function createMarkerSource(options) {
45
+ const client = projectedSnapshot(options.getSnapshot, options.project);
46
+ const server = options.getServerSnapshot === undefined
47
+ ? client
48
+ : projectedSnapshot(options.getServerSnapshot, options.project);
49
+ return {
50
+ subscribe(listener) {
51
+ if (options.subscribe === undefined)
52
+ return () => undefined;
53
+ return options.subscribe(() => {
54
+ client.invalidate();
55
+ listener();
56
+ });
57
+ },
58
+ getSnapshot: client.read,
59
+ getServerSnapshot: server.read,
60
+ };
61
+ }
15
62
  function distanceXZ(a, b) {
16
63
  return Math.hypot(a[0] - b[0], a[2] - b[2]);
17
64
  }
@@ -38,6 +85,8 @@ export function createMarkerSet(now = Date.now) {
38
85
  };
39
86
  if (input.label !== undefined)
40
87
  marker.label = input.label;
88
+ if (input.heading !== undefined)
89
+ marker.heading = input.heading;
41
90
  if (input.owner !== undefined)
42
91
  marker.owner = input.owner;
43
92
  if (input.expiresAt !== undefined)
@@ -1,3 +1,4 @@
1
+ import { clamp } from "../math/scalar.js";
1
2
  export const MASSING_COMPOSITIONS = [
2
3
  "bar",
3
4
  "split",
@@ -17,7 +18,6 @@ export const MASSING_PROFILES = [
17
18
  "offset",
18
19
  "twisted",
19
20
  ];
20
- const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
21
21
  export const hashMassingSeed = (text) => [...text].reduce((sum, ch) => sum + ch.charCodeAt(0), 0);
22
22
  export const massingBase = (spec, cap = 0.28) => Math.min(Math.max(0, spec.pilotis), Math.max(0, spec.height) * cap);
23
23
  export const isVillageMassing = (spec) => spec.composition === "cluster" && spec.height < 36 && spec.articulation > 56 && spec.moduleDensity > 2;
@@ -0,0 +1,78 @@
1
+ import type { Vec2 } from "./geometry.js";
2
+ /**
3
+ * Deterministic minimap terrain bake (#1036): rasterize an authored world's terrain (plus optional
4
+ * biome zones and water) into a top-down RGBA image and a matching world-bounds rectangle, then
5
+ * encode it as a PNG data URI. The editor runs this as a bake action and stores the result on the
6
+ * scene document; runtime feeds it straight into the existing `Minimap` / `WorldMap` `background` +
7
+ * `mapBounds` props — no new runtime render path, and the same authored world always bakes the same
8
+ * image (safe for verify/CI). Pure: no canvas, no DOM, `core` still imports nothing external.
9
+ */
10
+ /** World-space rectangle the bake spans (matches the react `MapBounds` shape structurally). */
11
+ export interface MinimapBakeBounds {
12
+ minX: number;
13
+ minZ: number;
14
+ maxX: number;
15
+ maxZ: number;
16
+ }
17
+ /** One biome/region tint painted over the terrain shade where the ground falls inside its polygon. */
18
+ export interface MinimapBakeZone {
19
+ /** Closed XZ polygon in world space. */
20
+ polygon: readonly Vec2[];
21
+ /** `#rgb` / `#rrggbb` tint. */
22
+ color: string;
23
+ /** Blend strength 0..1. Default 0.5. */
24
+ alpha?: number;
25
+ }
26
+ /** The authored world a bake reads — a height sampler, its bounds, and optional zones/water. */
27
+ export interface MinimapBakeSource {
28
+ bounds: MinimapBakeBounds;
29
+ sampleHeight(x: number, z: number): number;
30
+ /** Optional surface normal for slope shading; flat shading when omitted. */
31
+ sampleNormal?(x: number, z: number): readonly [number, number, number];
32
+ zones?: readonly MinimapBakeZone[];
33
+ /** Ground at or below this height renders as water. */
34
+ waterLevel?: number;
35
+ }
36
+ /** The height→color ramp and water color used by the bake. */
37
+ export interface MinimapBakePalette {
38
+ low: string;
39
+ high: string;
40
+ water: string;
41
+ }
42
+ /** The built-in height→color ramp and water color a bake uses when no palette override is given. */
43
+ export declare const DEFAULT_MINIMAP_PALETTE: MinimapBakePalette;
44
+ /** Options for {@link bakeMinimapImage}. */
45
+ export interface MinimapBakeOptions {
46
+ /** Pixel resolution of the longer axis; the shorter axis keeps world aspect. Default 128. */
47
+ resolution?: number;
48
+ palette?: Partial<MinimapBakePalette>;
49
+ }
50
+ /** A baked minimap image: RGBA pixels plus the world bounds they span. */
51
+ export interface MinimapBake {
52
+ width: number;
53
+ height: number;
54
+ /** Row-major RGBA, length `width * height * 4`. */
55
+ pixels: Uint8ClampedArray;
56
+ bounds: MinimapBakeBounds;
57
+ }
58
+ /**
59
+ * Rasterizes a top-down terrain image: height maps onto a low→high color ramp (normalized over the
60
+ * sampled height range), slope darkens the shade, water fills below `waterLevel`, and each zone
61
+ * polygon tints the ground it covers. Deterministic for a fixed source. The bounds are preserved so
62
+ * a player-centered minimap pans the image correctly.
63
+ * @capability minimap-bake rasterize authored terrain + zones into a top-down minimap image
64
+ */
65
+ export declare function bakeMinimapImage(source: MinimapBakeSource, options?: MinimapBakeOptions): MinimapBake;
66
+ /** Encodes an RGBA bake to raw PNG bytes (pure; stored DEFLATE, valid for any decoder). */
67
+ export declare function encodeBakePng(bake: MinimapBake): Uint8Array;
68
+ /**
69
+ * Encodes a bake as a `data:image/png;base64,...` URI — the exact string the `Minimap` /
70
+ * `WorldMap` `background` prop takes. Deterministic for a fixed bake.
71
+ * @capability minimap-bake encode a baked minimap as a PNG data URI for the Minimap background
72
+ */
73
+ export declare function minimapBakeToPngDataUri(bake: MinimapBake): string;
74
+ /** One-call convenience: rasterize a source and return `{ background, mapBounds }` for a minimap. */
75
+ export declare function bakeMinimapBackground(source: MinimapBakeSource, options?: MinimapBakeOptions): {
76
+ background: string;
77
+ mapBounds: MinimapBakeBounds;
78
+ };