@jgengine/core 0.8.0 → 0.9.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 (228) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/ai/flock.d.ts +36 -0
  3. package/dist/ai/flock.js +182 -0
  4. package/dist/ai/groupAssist.d.ts +19 -0
  5. package/dist/ai/groupAssist.js +59 -0
  6. package/dist/ai/laneSelect.d.ts +21 -0
  7. package/dist/ai/laneSelect.js +37 -0
  8. package/dist/ai/mobBrain.d.ts +68 -0
  9. package/dist/ai/mobBrain.js +115 -0
  10. package/dist/ai/spawnDirector.js +1 -1
  11. package/dist/ai/threat.d.ts +2 -0
  12. package/dist/ai/threat.js +34 -0
  13. package/dist/cartridge/runtime.d.ts +15 -0
  14. package/dist/cartridge/runtime.js +556 -0
  15. package/dist/cartridge/spec.d.ts +188 -0
  16. package/dist/cartridge/spec.js +19 -0
  17. package/dist/cartridge/validate.d.ts +2 -0
  18. package/dist/cartridge/validate.js +78 -0
  19. package/dist/combat/abilityKit.d.ts +17 -2
  20. package/dist/combat/abilityKit.js +62 -13
  21. package/dist/combat/castRunner.d.ts +40 -0
  22. package/dist/combat/castRunner.js +56 -0
  23. package/dist/combat/comboPoints.d.ts +14 -0
  24. package/dist/combat/comboPoints.js +52 -0
  25. package/dist/combat/defensiveWindow.d.ts +1 -1
  26. package/dist/combat/defensiveWindow.js +5 -3
  27. package/dist/combat/projectiles.d.ts +33 -12
  28. package/dist/combat/projectiles.js +201 -129
  29. package/dist/combat/resistance.d.ts +16 -4
  30. package/dist/combat/resistance.js +43 -7
  31. package/dist/combat/resourcePool.d.ts +20 -0
  32. package/dist/combat/resourcePool.js +55 -0
  33. package/dist/combat/shotOrigin.d.ts +40 -0
  34. package/dist/combat/shotOrigin.js +57 -0
  35. package/dist/combat/telegraph.d.ts +25 -0
  36. package/dist/combat/telegraph.js +47 -0
  37. package/dist/crafting/recipe.js +4 -1
  38. package/dist/devtools/devtools.d.ts +72 -24
  39. package/dist/devtools/devtools.js +449 -101
  40. package/dist/devtools/transformTunables.js +3 -3
  41. package/dist/devtools/tunableSchema.d.ts +140 -0
  42. package/dist/devtools/tunableSchema.js +476 -0
  43. package/dist/faction/factions.d.ts +33 -0
  44. package/dist/faction/factions.js +76 -0
  45. package/dist/faction/reputation.d.ts +32 -0
  46. package/dist/faction/reputation.js +82 -0
  47. package/dist/game/chat.d.ts +2 -0
  48. package/dist/game/chat.js +9 -1
  49. package/dist/game/chatFilter.d.ts +15 -0
  50. package/dist/game/chatFilter.js +55 -0
  51. package/dist/game/controlGate.d.ts +4 -0
  52. package/dist/game/controlGate.js +7 -0
  53. package/dist/game/defineGame.d.ts +2 -0
  54. package/dist/game/events.d.ts +10 -0
  55. package/dist/game/gamePhase.d.ts +20 -0
  56. package/dist/game/gamePhase.js +21 -0
  57. package/dist/game/lootTable.d.ts +10 -1
  58. package/dist/game/lootTable.js +27 -7
  59. package/dist/game/playableGame.d.ts +50 -1
  60. package/dist/game/playableGame.js +1 -1
  61. package/dist/game/quest.d.ts +7 -3
  62. package/dist/game/quest.js +17 -8
  63. package/dist/game/race.d.ts +39 -1
  64. package/dist/game/race.js +96 -11
  65. package/dist/game/recordBook.d.ts +29 -0
  66. package/dist/game/recordBook.js +86 -0
  67. package/dist/game/talents.d.ts +47 -0
  68. package/dist/game/talents.js +140 -0
  69. package/dist/index.d.ts +1 -0
  70. package/dist/index.js +1 -0
  71. package/dist/input/axisInput.d.ts +26 -3
  72. package/dist/input/axisInput.js +44 -4
  73. package/dist/input/bindingOverrides.d.ts +25 -0
  74. package/dist/input/bindingOverrides.js +95 -0
  75. package/dist/input/pointerAxis.d.ts +30 -0
  76. package/dist/input/pointerAxis.js +31 -0
  77. package/dist/input/touchScheme.d.ts +6 -0
  78. package/dist/input/touchScheme.js +45 -7
  79. package/dist/meta/changelog.d.ts +1 -1
  80. package/dist/meta/changelog.js +23 -1
  81. package/dist/movement/dash.d.ts +3 -0
  82. package/dist/movement/dash.js +19 -5
  83. package/dist/movement/glideModel.d.ts +44 -0
  84. package/dist/movement/glideModel.js +54 -0
  85. package/dist/movement/grappleSwing.d.ts +34 -0
  86. package/dist/movement/grappleSwing.js +56 -0
  87. package/dist/movement/leaderTrail.d.ts +29 -0
  88. package/dist/movement/leaderTrail.js +67 -0
  89. package/dist/movement/steering.d.ts +27 -0
  90. package/dist/movement/steering.js +40 -0
  91. package/dist/multiplayer/feedWriteGate.d.ts +10 -0
  92. package/dist/multiplayer/feedWriteGate.js +12 -0
  93. package/dist/multiplayer/presenceModel.d.ts +14 -2
  94. package/dist/multiplayer/presenceModel.js +15 -3
  95. package/dist/nav/corridors.d.ts +30 -0
  96. package/dist/nav/corridors.js +69 -0
  97. package/dist/nav/navGrid.js +30 -4
  98. package/dist/nav/railGraph.d.ts +66 -0
  99. package/dist/nav/railGraph.js +129 -0
  100. package/dist/nav/timetable.d.ts +41 -0
  101. package/dist/nav/timetable.js +94 -0
  102. package/dist/physics/buoyancy.d.ts +6 -0
  103. package/dist/physics/buoyancy.js +14 -0
  104. package/dist/physics/flowTube.d.ts +32 -0
  105. package/dist/physics/flowTube.js +64 -0
  106. package/dist/physics/forceVolume.d.ts +27 -0
  107. package/dist/physics/forceVolume.js +38 -0
  108. package/dist/physics/kinematicVehicle.d.ts +52 -0
  109. package/dist/physics/kinematicVehicle.js +81 -0
  110. package/dist/physics/physicsWorld.d.ts +13 -6
  111. package/dist/physics/physicsWorld.js +169 -40
  112. package/dist/physics/ragdoll.js +1 -1
  113. package/dist/physics/vehicleBody.js +2 -1
  114. package/dist/random/seedLink.d.ts +5 -0
  115. package/dist/random/seedLink.js +35 -0
  116. package/dist/runtime/cameraDirector.d.ts +6 -1
  117. package/dist/runtime/cameraDirector.js +6 -0
  118. package/dist/runtime/commandRunner.d.ts +2 -2
  119. package/dist/runtime/commandRunner.js +2 -2
  120. package/dist/runtime/gameContext.d.ts +21 -0
  121. package/dist/runtime/gameContext.js +184 -9
  122. package/dist/runtime/gameRuntime.d.ts +5 -1
  123. package/dist/runtime/gameRuntime.js +13 -12
  124. package/dist/runtime/hostPersistence.d.ts +9 -1
  125. package/dist/runtime/hostPersistence.js +23 -8
  126. package/dist/runtime/inputSnapshot.d.ts +5 -0
  127. package/dist/runtime/inputSnapshot.js +5 -0
  128. package/dist/runtime/motionIntents.d.ts +14 -6
  129. package/dist/runtime/motionIntents.js +20 -2
  130. package/dist/runtime/snapshot.d.ts +1 -0
  131. package/dist/runtime/snapshot.js +1 -1
  132. package/dist/runtime/visibility.d.ts +74 -0
  133. package/dist/runtime/visibility.js +127 -0
  134. package/dist/scene/colliders.d.ts +44 -0
  135. package/dist/scene/colliders.js +77 -0
  136. package/dist/scene/entityStore.d.ts +22 -5
  137. package/dist/scene/entityStore.js +59 -12
  138. package/dist/scene/objectQuery.d.ts +13 -2
  139. package/dist/scene/objectQuery.js +34 -2
  140. package/dist/scene/objectStore.d.ts +2 -0
  141. package/dist/scene/objectStore.js +31 -12
  142. package/dist/scene/sceneRaycast.d.ts +80 -0
  143. package/dist/scene/sceneRaycast.js +284 -0
  144. package/dist/scene/spatial.d.ts +11 -16
  145. package/dist/scene/spatial.js +99 -38
  146. package/dist/sensor/recordingBuffer.d.ts +6 -0
  147. package/dist/sensor/recordingBuffer.js +7 -0
  148. package/dist/sensor/replayLoop.d.ts +39 -0
  149. package/dist/sensor/replayLoop.js +64 -0
  150. package/dist/sensor/visionCone.d.ts +31 -0
  151. package/dist/sensor/visionCone.js +72 -0
  152. package/dist/session/extraction.d.ts +2 -2
  153. package/dist/session/extraction.js +13 -2
  154. package/dist/settings/settingsModel.d.ts +97 -0
  155. package/dist/settings/settingsModel.js +100 -0
  156. package/dist/stats/statModifiers.d.ts +5 -2
  157. package/dist/stats/statModifiers.js +32 -14
  158. package/dist/store/observableKeyedStore.js +8 -2
  159. package/dist/survival/decayMeter.d.ts +1 -1
  160. package/dist/survival/decayMeter.js +0 -1
  161. package/dist/time/beatClock.d.ts +18 -0
  162. package/dist/time/beatClock.js +24 -0
  163. package/dist/time/idleProgress.d.ts +29 -0
  164. package/dist/time/idleProgress.js +31 -0
  165. package/dist/time/serverTick.d.ts +7 -1
  166. package/dist/time/serverTick.js +27 -4
  167. package/dist/time/stateSchedule.d.ts +57 -0
  168. package/dist/time/stateSchedule.js +121 -0
  169. package/dist/turn/commit.d.ts +2 -2
  170. package/dist/turn/commit.js +2 -2
  171. package/dist/ui/hudLayout.d.ts +58 -0
  172. package/dist/ui/hudLayout.js +207 -0
  173. package/dist/ui/hudScale.d.ts +44 -0
  174. package/dist/ui/hudScale.js +55 -0
  175. package/dist/visibility/assetStreaming.d.ts +66 -0
  176. package/dist/visibility/assetStreaming.js +201 -0
  177. package/dist/visibility/bounds.d.ts +66 -0
  178. package/dist/visibility/bounds.js +113 -0
  179. package/dist/visibility/camera.d.ts +21 -0
  180. package/dist/visibility/camera.js +3 -0
  181. package/dist/visibility/config.d.ts +21 -0
  182. package/dist/visibility/config.js +1 -0
  183. package/dist/visibility/diagnostics.d.ts +26 -0
  184. package/dist/visibility/diagnostics.js +36 -0
  185. package/dist/visibility/distance.d.ts +11 -0
  186. package/dist/visibility/distance.js +30 -0
  187. package/dist/visibility/frustum.d.ts +47 -0
  188. package/dist/visibility/frustum.js +186 -0
  189. package/dist/visibility/occlusion.d.ts +36 -0
  190. package/dist/visibility/occlusion.js +45 -0
  191. package/dist/visibility/settings.d.ts +75 -0
  192. package/dist/visibility/settings.js +49 -0
  193. package/dist/visibility/simulationCulling.d.ts +34 -0
  194. package/dist/visibility/simulationCulling.js +36 -0
  195. package/dist/visibility/spatialIndex.d.ts +45 -0
  196. package/dist/visibility/spatialIndex.js +198 -0
  197. package/dist/visibility/visibilitySystem.d.ts +90 -0
  198. package/dist/visibility/visibilitySystem.js +257 -0
  199. package/dist/world/buildings.d.ts +6 -0
  200. package/dist/world/buildings.js +152 -0
  201. package/dist/world/carve.d.ts +7 -2
  202. package/dist/world/carve.js +17 -7
  203. package/dist/world/cellStates.d.ts +45 -0
  204. package/dist/world/cellStates.js +103 -0
  205. package/dist/world/environmentSummary.d.ts +10 -2
  206. package/dist/world/environmentSummary.js +11 -2
  207. package/dist/world/features.d.ts +56 -8
  208. package/dist/world/features.js +24 -3
  209. package/dist/world/gridInstances.d.ts +7 -8
  210. package/dist/world/gridInstances.js +145 -7
  211. package/dist/world/lod.d.ts +17 -0
  212. package/dist/world/lod.js +66 -0
  213. package/dist/world/mapLayers.d.ts +60 -0
  214. package/dist/world/mapLayers.js +44 -0
  215. package/dist/world/massing.d.ts +45 -0
  216. package/dist/world/massing.js +581 -0
  217. package/dist/world/minimap.d.ts +13 -1
  218. package/dist/world/minimap.js +21 -2
  219. package/dist/world/terrain.d.ts +42 -3
  220. package/dist/world/terrain.js +197 -16
  221. package/dist/world/water.d.ts +7 -0
  222. package/dist/world/water.js +19 -4
  223. package/dist/world/weather.d.ts +3 -18
  224. package/dist/world/weather.js +4 -17
  225. package/dist/world/windZones.d.ts +47 -0
  226. package/dist/world/windZones.js +59 -0
  227. package/llms.txt +2585 -1704
  228. package/package.json +12 -2
@@ -33,6 +33,158 @@ export const DEFAULT_BUILDING_CONFIG = {
33
33
  probabilities: DEFAULT_PROBABILITIES,
34
34
  variants: DEFAULT_VARIANTS,
35
35
  };
36
+ export const DEFAULT_BUILDING_STYLE = "generic";
37
+ export const BUILDING_STYLE_PALETTES = {
38
+ generic: {
39
+ wall: "#83766a",
40
+ window: "#8ecae6",
41
+ awning: "#c2410c",
42
+ airConditioner: "#d4d4d8",
43
+ clothesline: "#facc15",
44
+ storefront: "#3f3f46",
45
+ shutter: "#52525b",
46
+ storeSign: "#f97316",
47
+ roof: "#57534e",
48
+ roofProp: "#14b8a6",
49
+ guardrail: "#a8a29e",
50
+ corner: "#6b6258",
51
+ },
52
+ capital: {
53
+ wall: "#cfc5ad",
54
+ window: "#33506e",
55
+ awning: "#7a1f2b",
56
+ airConditioner: "#b9b3a4",
57
+ clothesline: "#d9cfae",
58
+ storefront: "#4a3f33",
59
+ shutter: "#5d5647",
60
+ storeSign: "#d4af37",
61
+ roof: "#3b4252",
62
+ roofProp: "#8c7a4e",
63
+ guardrail: "#a89f88",
64
+ corner: "#b3a98d",
65
+ },
66
+ village: {
67
+ wall: "#b59e7a",
68
+ window: "#e9c46a",
69
+ awning: "#7f5539",
70
+ airConditioner: "#a8a29e",
71
+ clothesline: "#e07a5f",
72
+ storefront: "#5c4a36",
73
+ shutter: "#6e5a40",
74
+ storeSign: "#c8894a",
75
+ roof: "#8a5a3b",
76
+ roofProp: "#97724d",
77
+ guardrail: "#8f8271",
78
+ corner: "#8d7354",
79
+ },
80
+ desert: {
81
+ wall: "#c9a877",
82
+ window: "#3f6f6b",
83
+ awning: "#a3512e",
84
+ airConditioner: "#c9bfa8",
85
+ clothesline: "#d9b16f",
86
+ storefront: "#6e5233",
87
+ shutter: "#7d6140",
88
+ storeSign: "#b8742f",
89
+ roof: "#b5946a",
90
+ roofProp: "#9a7648",
91
+ guardrail: "#b09b77",
92
+ corner: "#a98c5d",
93
+ },
94
+ industrial: {
95
+ wall: "#6b7280",
96
+ window: "#9fb3c8",
97
+ awning: "#f97316",
98
+ airConditioner: "#9ca3af",
99
+ clothesline: "#eab308",
100
+ storefront: "#374151",
101
+ shutter: "#4b5563",
102
+ storeSign: "#f59e0b",
103
+ roof: "#374151",
104
+ roofProp: "#ef4444",
105
+ guardrail: "#d1d5db",
106
+ corner: "#52596b",
107
+ },
108
+ coastal: {
109
+ wall: "#e8e2d2",
110
+ window: "#7cc3d8",
111
+ awning: "#e76f51",
112
+ airConditioner: "#cfd8dc",
113
+ clothesline: "#f2cc8f",
114
+ storefront: "#35526b",
115
+ shutter: "#3a7ca5",
116
+ storeSign: "#ee6c4d",
117
+ roof: "#4f6d7a",
118
+ roofProp: "#62b6cb",
119
+ guardrail: "#c5ccc2",
120
+ corner: "#cfc5ab",
121
+ },
122
+ neon: {
123
+ wall: "#1b1b26",
124
+ window: "#22d3ee",
125
+ awning: "#ff2d95",
126
+ airConditioner: "#3f3f50",
127
+ clothesline: "#a78bfa",
128
+ storefront: "#12121a",
129
+ shutter: "#2a2a3a",
130
+ storeSign: "#f0abfc",
131
+ roof: "#14141d",
132
+ roofProp: "#34d399",
133
+ guardrail: "#52525f",
134
+ corner: "#24242f",
135
+ },
136
+ ruin: {
137
+ wall: "#6b6156",
138
+ window: "#1c1917",
139
+ awning: "#4a4238",
140
+ airConditioner: "#57534e",
141
+ clothesline: "#6b7f4f",
142
+ storefront: "#292524",
143
+ shutter: "#44403c",
144
+ storeSign: "#78716c",
145
+ roof: "#4a443e",
146
+ roofProp: "#6b7f4f",
147
+ guardrail: "#7c7268",
148
+ corner: "#57503f",
149
+ },
150
+ frontier: {
151
+ wall: "#7f5539",
152
+ window: "#d9ae62",
153
+ awning: "#6e3f1f",
154
+ airConditioner: "#8d8072",
155
+ clothesline: "#c98a5e",
156
+ storefront: "#4a3220",
157
+ shutter: "#5c3d26",
158
+ storeSign: "#b3702f",
159
+ roof: "#9c4a1a",
160
+ roofProp: "#7d6b52",
161
+ guardrail: "#937b5f",
162
+ corner: "#66492f",
163
+ },
164
+ aerial: {
165
+ wall: "#dfe7ec",
166
+ window: "#67c3cc",
167
+ awning: "#4d7ea8",
168
+ airConditioner: "#b8c4cc",
169
+ clothesline: "#9fd8df",
170
+ storefront: "#5c7684",
171
+ shutter: "#7b95a3",
172
+ storeSign: "#3aa7b8",
173
+ roof: "#aebcc4",
174
+ roofProp: "#e0f2f5",
175
+ guardrail: "#98a8b0",
176
+ corner: "#c3d0d7",
177
+ },
178
+ };
179
+ export function resolveBuildingPalette(style = DEFAULT_BUILDING_STYLE, overrides) {
180
+ const preset = BUILDING_STYLE_PALETTES[style];
181
+ if (preset === undefined) {
182
+ throw new Error(`Unknown building style "${style}". Valid styles: ${Object.keys(BUILDING_STYLE_PALETTES).join(", ")}. Use palette: { wall, window, ... } for a custom look.`);
183
+ }
184
+ if (overrides === undefined)
185
+ return preset;
186
+ return { ...preset, ...overrides };
187
+ }
36
188
  function hashU32(input) {
37
189
  let hash = 2166136261;
38
190
  for (let index = 0; index < input.length; index += 1) {
@@ -95,11 +95,16 @@ export declare class CarvableField implements TerrainField {
95
95
  readonly sampleNormal: TerrainField["sampleNormal"];
96
96
  private readonly base;
97
97
  private readonly edits;
98
+ private nextEditId;
98
99
  constructor(base: TerrainField);
99
100
  get editCount(): number;
100
101
  sampleHeight(x: number, z: number): number;
101
- carve(op: CraterOp): void;
102
- deposit(op: MoundOp): void;
102
+ /** Digs a bowl; the returned id undoes just this crater via `removeEdit` (#284.7). */
103
+ carve(op: CraterOp): number;
104
+ /** Raises a mound; the returned id undoes just this mound via `removeEdit` (#284.7). */
105
+ deposit(op: MoundOp): number;
106
+ /** Removes one edit by the id `carve`/`deposit` returned — capped-history destructibles evict without rebuilding the field. */
107
+ removeEdit(editId: number): boolean;
103
108
  clear(): void;
104
109
  }
105
110
  export declare function carvableTerrain(base: TerrainField): CarvableField;
@@ -135,7 +135,8 @@ export class CarvableField {
135
135
  waterLevel;
136
136
  sampleNormal;
137
137
  base;
138
- edits = [];
138
+ edits = new Map();
139
+ nextEditId = 1;
139
140
  constructor(base) {
140
141
  this.base = base;
141
142
  if (base.bounds !== undefined)
@@ -145,12 +146,11 @@ export class CarvableField {
145
146
  this.sampleNormal = withNormal((x, z) => this.sampleHeight(x, z));
146
147
  }
147
148
  get editCount() {
148
- return this.edits.length;
149
+ return this.edits.size;
149
150
  }
150
151
  sampleHeight(x, z) {
151
152
  let h = this.base.sampleHeight(x, z);
152
- for (let i = 0; i < this.edits.length; i += 1) {
153
- const e = this.edits[i];
153
+ for (const e of this.edits.values()) {
154
154
  const dx = x - e.x;
155
155
  const dz = z - e.z;
156
156
  const d = Math.sqrt(dx * dx + dz * dz);
@@ -160,14 +160,24 @@ export class CarvableField {
160
160
  }
161
161
  return h;
162
162
  }
163
+ /** Digs a bowl; the returned id undoes just this crater via `removeEdit` (#284.7). */
163
164
  carve(op) {
164
- this.edits.push({ x: op.x, z: op.z, radius: op.radius, delta: -op.depth });
165
+ const id = this.nextEditId++;
166
+ this.edits.set(id, { x: op.x, z: op.z, radius: op.radius, delta: -op.depth });
167
+ return id;
165
168
  }
169
+ /** Raises a mound; the returned id undoes just this mound via `removeEdit` (#284.7). */
166
170
  deposit(op) {
167
- this.edits.push({ x: op.x, z: op.z, radius: op.radius, delta: op.height });
171
+ const id = this.nextEditId++;
172
+ this.edits.set(id, { x: op.x, z: op.z, radius: op.radius, delta: op.height });
173
+ return id;
174
+ }
175
+ /** Removes one edit by the id `carve`/`deposit` returned — capped-history destructibles evict without rebuilding the field. */
176
+ removeEdit(editId) {
177
+ return this.edits.delete(editId);
168
178
  }
169
179
  clear() {
170
- this.edits.length = 0;
180
+ this.edits.clear();
171
181
  }
172
182
  }
173
183
  export function carvableTerrain(base) {
@@ -0,0 +1,45 @@
1
+ export interface CellStateGridConfig<TState extends string> {
2
+ cols: number;
3
+ rows: number;
4
+ /** The ordered escalation ladder; every cell starts at `states[0]`. */
5
+ states: readonly TState[];
6
+ /** World-space center of cell (0, 0), for `cellOf` world→cell mapping. Default `[0, 0]`. */
7
+ origin?: readonly [number, number];
8
+ /** World units per cell, for `cellOf`. Default `1`. */
9
+ cellSize?: number;
10
+ }
11
+ export interface CellRef {
12
+ col: number;
13
+ row: number;
14
+ }
15
+ /**
16
+ * A uniform world grid whose cells step through a discrete state ladder — pristine → cracked →
17
+ * burning → ruined — driven by whatever world events the game routes in (#284.8). Pure storage plus
18
+ * ladder mechanics; the game decides *when* to escalate, the grid guarantees ordered, clamped steps
19
+ * and cheap queries. `version()` bumps on every change for dirty-checked rendering.
20
+ */
21
+ export interface CellStateGrid<TState extends string> {
22
+ readonly cols: number;
23
+ readonly rows: number;
24
+ readonly states: readonly TState[];
25
+ stateAt(col: number, row: number): TState | null;
26
+ /** Ladder index at a cell; `-1` outside the grid. */
27
+ indexAt(col: number, row: number): number;
28
+ /** Step a cell up the ladder (clamped at the last state); returns the new state or `null` outside the grid. */
29
+ escalate(col: number, row: number, steps?: number): TState | null;
30
+ /** Step a cell back down (clamped at the first state). */
31
+ regress(col: number, row: number, steps?: number): TState | null;
32
+ set(col: number, row: number, state: TState): boolean;
33
+ /** Escalate every cell the predicate matches; returns how many changed. */
34
+ escalateWhere(predicate: (col: number, row: number, state: TState) => boolean, steps?: number): number;
35
+ cellsIn(state: TState): CellRef[];
36
+ counts(): Record<TState, number>;
37
+ /** World position → cell, using `origin`/`cellSize`; `null` outside the grid. */
38
+ cellOf(x: number, z: number): CellRef | null;
39
+ /** World-space center of a cell. */
40
+ centerOf(col: number, row: number): readonly [number, number];
41
+ /** Bumps on every state change — key re-renders/dirty checks off it. */
42
+ version(): number;
43
+ reset(): void;
44
+ }
45
+ export declare function createCellStateGrid<TState extends string>(config: CellStateGridConfig<TState>): CellStateGrid<TState>;
@@ -0,0 +1,103 @@
1
+ export function createCellStateGrid(config) {
2
+ if (config.states.length === 0)
3
+ throw new Error("cell state grid needs at least one state");
4
+ if (!(config.cols > 0) || !(config.rows > 0)) {
5
+ throw new Error(`cell state grid needs positive dimensions, got ${config.cols}x${config.rows}`);
6
+ }
7
+ const stateIndex = new Map();
8
+ config.states.forEach((state, index) => {
9
+ if (stateIndex.has(state))
10
+ throw new Error(`duplicate cell state: ${state}`);
11
+ stateIndex.set(state, index);
12
+ });
13
+ const [originX, originZ] = config.origin ?? [0, 0];
14
+ const cellSize = config.cellSize ?? 1;
15
+ const cells = new Int32Array(config.cols * config.rows);
16
+ let version = 0;
17
+ function inBounds(col, row) {
18
+ return Number.isInteger(col) && Number.isInteger(row) && col >= 0 && col < config.cols && row >= 0 && row < config.rows;
19
+ }
20
+ function indexOf(col, row) {
21
+ return row * config.cols + col;
22
+ }
23
+ function step(col, row, steps) {
24
+ if (!inBounds(col, row))
25
+ return null;
26
+ const at = indexOf(col, row);
27
+ const next = Math.max(0, Math.min(config.states.length - 1, cells[at] + steps));
28
+ if (next !== cells[at]) {
29
+ cells[at] = next;
30
+ version += 1;
31
+ }
32
+ return config.states[next];
33
+ }
34
+ return {
35
+ cols: config.cols,
36
+ rows: config.rows,
37
+ states: config.states,
38
+ stateAt: (col, row) => (inBounds(col, row) ? config.states[cells[indexOf(col, row)]] : null),
39
+ indexAt: (col, row) => (inBounds(col, row) ? cells[indexOf(col, row)] : -1),
40
+ escalate: (col, row, steps = 1) => step(col, row, Math.max(0, steps)),
41
+ regress: (col, row, steps = 1) => step(col, row, -Math.max(0, steps)),
42
+ set(col, row, state) {
43
+ const target = stateIndex.get(state);
44
+ if (target === undefined || !inBounds(col, row))
45
+ return false;
46
+ const at = indexOf(col, row);
47
+ if (cells[at] !== target) {
48
+ cells[at] = target;
49
+ version += 1;
50
+ }
51
+ return true;
52
+ },
53
+ escalateWhere(predicate, steps = 1) {
54
+ let changed = 0;
55
+ for (let row = 0; row < config.rows; row += 1) {
56
+ for (let col = 0; col < config.cols; col += 1) {
57
+ const at = indexOf(col, row);
58
+ if (!predicate(col, row, config.states[cells[at]]))
59
+ continue;
60
+ const next = Math.min(config.states.length - 1, cells[at] + Math.max(0, steps));
61
+ if (next !== cells[at]) {
62
+ cells[at] = next;
63
+ changed += 1;
64
+ }
65
+ }
66
+ }
67
+ if (changed > 0)
68
+ version += 1;
69
+ return changed;
70
+ },
71
+ cellsIn(state) {
72
+ const target = stateIndex.get(state);
73
+ const refs = [];
74
+ if (target === undefined)
75
+ return refs;
76
+ for (let row = 0; row < config.rows; row += 1) {
77
+ for (let col = 0; col < config.cols; col += 1) {
78
+ if (cells[indexOf(col, row)] === target)
79
+ refs.push({ col, row });
80
+ }
81
+ }
82
+ return refs;
83
+ },
84
+ counts() {
85
+ const result = Object.fromEntries(config.states.map((state) => [state, 0]));
86
+ for (let at = 0; at < cells.length; at += 1) {
87
+ result[config.states[cells[at]]] += 1;
88
+ }
89
+ return result;
90
+ },
91
+ cellOf(x, z) {
92
+ const col = Math.round((x - originX) / cellSize);
93
+ const row = Math.round((z - originZ) / cellSize);
94
+ return inBounds(col, row) ? { col, row } : null;
95
+ },
96
+ centerOf: (col, row) => [originX + col * cellSize, originZ + row * cellSize],
97
+ version: () => version,
98
+ reset() {
99
+ cells.fill(0);
100
+ version += 1;
101
+ },
102
+ };
103
+ }
@@ -1,6 +1,7 @@
1
- import { type GeneratedBuilding } from "./buildings.js";
1
+ import { type BuildingPalette, type BuildingStyle, type GeneratedBuilding } from "./buildings.js";
2
2
  import type { BuildingEnvironmentDescriptor, EnvironmentWorldFeature, WorldBounds } from "./features.js";
3
3
  import type { Aabb } from "./geometry.js";
4
+ import { type TerrainPalette } from "./terrain.js";
4
5
  export declare function resolveStructureBuildings(descriptor: BuildingEnvironmentDescriptor): GeneratedBuilding[];
5
6
  export interface TerrainHeightStats {
6
7
  min: number;
@@ -11,10 +12,12 @@ export interface TerrainHeightStats {
11
12
  export interface TerrainSummary {
12
13
  bounds: WorldBounds;
13
14
  height: TerrainHeightStats;
15
+ palette: TerrainPalette;
14
16
  waterLevel?: number;
15
17
  }
16
18
  export interface StructureSummary {
17
- style: string;
19
+ style: BuildingStyle;
20
+ palette: BuildingPalette;
18
21
  requested: number;
19
22
  buildings: number;
20
23
  parts: number;
@@ -34,6 +37,7 @@ export interface WeatherSummary {
34
37
  }
35
38
  export interface EnvironmentCounts {
36
39
  terrain: number;
40
+ islands: number;
37
41
  structureGroups: number;
38
42
  buildings: number;
39
43
  buildingParts: number;
@@ -41,8 +45,12 @@ export interface EnvironmentCounts {
41
45
  waterBodies: number;
42
46
  weatherSystems: number;
43
47
  }
48
+ export interface IslandSummary extends TerrainSummary {
49
+ origin: readonly [number, number];
50
+ }
44
51
  export interface EnvironmentSummary {
45
52
  terrain?: TerrainSummary;
53
+ islands: readonly IslandSummary[];
46
54
  structures: readonly StructureSummary[];
47
55
  vegetation: readonly VegetationSummary[];
48
56
  water: readonly WaterSummary[];
@@ -1,5 +1,5 @@
1
- import { generateBuildingDistrict } from "./buildings.js";
2
- import { resolveTerrainField } from "./terrain.js";
1
+ import { generateBuildingDistrict, resolveBuildingPalette, } from "./buildings.js";
2
+ import { resolveTerrainField, resolveTerrainPalette } from "./terrain.js";
3
3
  export function resolveStructureBuildings(descriptor) {
4
4
  const columns = Math.max(1, Math.ceil(Math.sqrt(descriptor.count)));
5
5
  const rows = Math.max(1, Math.ceil(descriptor.count / columns));
@@ -74,6 +74,7 @@ function summarizeStructures(descriptor) {
74
74
  const buildings = resolveStructureBuildings(descriptor);
75
75
  return {
76
76
  style: descriptor.style,
77
+ palette: resolveBuildingPalette(descriptor.style, descriptor.palette),
77
78
  requested: descriptor.count,
78
79
  buildings: buildings.length,
79
80
  parts: buildings.reduce((sum, building) => sum + building.parts.length, 0),
@@ -84,11 +85,16 @@ function summarizeTerrain(descriptor) {
84
85
  return {
85
86
  bounds: descriptor.bounds,
86
87
  height: sampleTerrainHeights(descriptor),
88
+ palette: resolveTerrainPalette(descriptor),
87
89
  ...(descriptor.waterLevel === undefined ? {} : { waterLevel: descriptor.waterLevel }),
88
90
  };
89
91
  }
90
92
  export function summarizeEnvironment(feature) {
91
93
  const terrain = feature.terrain === undefined ? undefined : summarizeTerrain(feature.terrain);
94
+ const islands = (feature.islands ?? []).map((entry) => ({
95
+ ...summarizeTerrain({ ...entry, kind: "terrain" }),
96
+ origin: entry.origin,
97
+ }));
92
98
  const structures = (feature.structures ?? []).map(summarizeStructures);
93
99
  const vegetation = (feature.vegetation ?? []).map((entry) => ({
94
100
  area: entry.area,
@@ -105,6 +111,7 @@ export function summarizeEnvironment(feature) {
105
111
  const buildings = structures.reduce((sum, entry) => sum + entry.buildings, 0);
106
112
  const counts = {
107
113
  terrain: terrain === undefined ? 0 : 1,
114
+ islands: islands.length,
108
115
  structureGroups: structures.length,
109
116
  buildings,
110
117
  buildingParts: structures.reduce((sum, entry) => sum + entry.parts, 0),
@@ -113,12 +120,14 @@ export function summarizeEnvironment(feature) {
113
120
  weatherSystems: weather.length,
114
121
  };
115
122
  const isEmpty = counts.terrain === 0 &&
123
+ counts.islands === 0 &&
116
124
  buildings === 0 &&
117
125
  counts.vegetationFields === 0 &&
118
126
  counts.waterBodies === 0 &&
119
127
  counts.weatherSystems === 0;
120
128
  return {
121
129
  ...(terrain === undefined ? {} : { terrain }),
130
+ islands,
122
131
  structures,
123
132
  vegetation,
124
133
  water,
@@ -1,10 +1,14 @@
1
+ import type { BuildingPaletteOverrides, BuildingStyle } from "./buildings.js";
1
2
  export interface WorldBounds {
2
3
  w: number;
3
4
  d: number;
4
5
  }
6
+ export type TerrainMaterial = "grass" | "sand" | "snow" | "rock" | "ash" | "highland" | "slate";
5
7
  export type EnvironmentVec2 = readonly [number, number];
6
8
  export interface EnvironmentArea extends WorldBounds {
7
9
  h?: number;
10
+ /** World-space center of the area; default `[0, 0]` (the world origin). Sites a weather/vegetation band away from the origin, like `building()`/`ocean()` `position`. */
11
+ position?: EnvironmentVec2;
8
12
  }
9
13
  export interface TerrainColors {
10
14
  low?: string;
@@ -19,14 +23,32 @@ export interface TerrainFlattenMask {
19
23
  /** Extra blend-ring width outside `radius` where the flat height smoothsteps back to the noise value; default `radius * 0.5`. */
20
24
  falloff?: number;
21
25
  }
26
+ /** A circular palette zone painted over the base terrain palette — snow caps, ash wastes, sand shores on one heightfield. */
27
+ export interface TerrainMaterialRegion {
28
+ center: EnvironmentVec2;
29
+ radius: number;
30
+ /** Named palette preset for this region (see `TERRAIN_MATERIAL_PALETTES` in `world/terrain`); overridden field-by-field by `colors`. */
31
+ material?: TerrainMaterial;
32
+ colors?: TerrainColors;
33
+ /** Blend-ring width outside `radius` back to the surrounding palette; default `radius * 0.5`. */
34
+ falloff?: number;
35
+ }
22
36
  export interface TerrainEnvironmentConfig {
23
37
  bounds?: WorldBounds;
24
38
  height?: number;
25
39
  heightMap?: string;
40
+ /**
41
+ * Game-supplied height function replacing the built-in noise field — banded biomes, ridge walls,
42
+ * terracing, anything expressible as `(x, z) => y`. `flatten` masks still carve into it, and both
43
+ * the rendered ground and `ctx.world.ground` sample it. A function doesn't serialize; keep it pure.
44
+ */
45
+ heightField?: (x: number, z: number) => number;
26
46
  /** Named palette preset (see `TERRAIN_MATERIAL_PALETTES` in `world/terrain`); default "grass". Overridden field-by-field by `colors`. */
27
- material?: string;
47
+ material?: TerrainMaterial;
28
48
  /** Explicit low/high/waterline hex colors; any field left unset falls back to the resolved `material` preset. */
29
49
  colors?: TerrainColors;
50
+ /** Palette zones blended over the base `material`/`colors` for multi-biome readability. */
51
+ materialRegions?: readonly TerrainMaterialRegion[];
30
52
  segments?: number;
31
53
  seed?: string;
32
54
  frequency?: number;
@@ -55,7 +77,7 @@ export interface SnowEnvironmentConfig {
55
77
  color?: string;
56
78
  }
57
79
  export interface GrassEnvironmentConfig {
58
- area?: WorldBounds;
80
+ area?: EnvironmentArea;
59
81
  density?: number;
60
82
  bladeHeight?: readonly [number, number];
61
83
  bladeWidth?: number;
@@ -67,7 +89,15 @@ export interface OceanEnvironmentConfig {
67
89
  bounds?: WorldBounds;
68
90
  position?: EnvironmentVec2;
69
91
  level?: number;
92
+ /**
93
+ * Scheduled water level as a function of game-time seconds — tides, floods, draining chambers.
94
+ * Wins over `level` (which stays the initial/static value); `waterSurfaceFromDescriptor` bakes it
95
+ * into `WaterSurface.height`, and the shell repositions the rendered ocean from it every frame.
96
+ * A function doesn't serialize; keep it pure and deterministic (compose with `time/stateSchedule`).
97
+ */
98
+ levelAt?: (time: number) => number;
70
99
  waveHeight?: number;
100
+ /** Primary Gerstner wavelength in world units (shared with shell Ocean `waveScale`; default 18). */
71
101
  waveScale?: number;
72
102
  waveSpeed?: number;
73
103
  color?: string;
@@ -94,7 +124,10 @@ export interface BuildingEnvironmentConfig {
94
124
  stories?: readonly [number, number];
95
125
  storyHeight?: number;
96
126
  spacing?: number;
97
- style?: string;
127
+ /** Named palette archetype (see `BUILDING_STYLE_PALETTES` in `world/buildings`); default "generic". Overridden part-by-part by `palette`. */
128
+ style?: BuildingStyle;
129
+ /** Explicit per-part-kind hex colors; any part left unset falls back to the resolved `style` palette. */
130
+ palette?: BuildingPaletteOverrides;
98
131
  seed?: string;
99
132
  }
100
133
  export type PadSize = readonly [number, number] | {
@@ -103,8 +136,10 @@ export type PadSize = readonly [number, number] | {
103
136
  export interface PadEnvironmentConfig {
104
137
  center: EnvironmentVec2;
105
138
  size: PadSize;
106
- /** Surface offset above the terrain height at `center`; default `0.05` — just proud of the ground. */
139
+ /** Surface offset above the terrain height at `center`; default `0.05` — just proud of the ground. Ignored when `elevation` is set. */
107
140
  height?: number;
141
+ /** Absolute world-space Y for the pad surface — a landing pad on a rooftop, a floating platform. Skips the implicit terrain flatten (there's no ground to flatten toward). */
142
+ elevation?: number;
108
143
  color?: string;
109
144
  /** Rotation applied to rectangular pads; ignored for circular pads. */
110
145
  rotationY?: number;
@@ -112,6 +147,15 @@ export interface PadEnvironmentConfig {
112
147
  export type TerrainEnvironmentDescriptor = {
113
148
  kind: "terrain";
114
149
  } & Required<Pick<TerrainEnvironmentConfig, "bounds" | "height">> & Omit<TerrainEnvironmentConfig, "bounds" | "height">;
150
+ /** A bounded terrain patch floating at its own altitude — sky islands, arena platforms, split landmasses with void between. */
151
+ export interface TerrainIslandConfig extends TerrainEnvironmentConfig {
152
+ /** World-space center of the island; its `bounds` rect surrounds this point. */
153
+ origin: EnvironmentVec2;
154
+ }
155
+ export type TerrainIslandDescriptor = Omit<TerrainEnvironmentDescriptor, "kind"> & {
156
+ kind: "island";
157
+ origin: EnvironmentVec2;
158
+ };
115
159
  export type RainEnvironmentDescriptor = {
116
160
  kind: "rain";
117
161
  } & Required<Pick<RainEnvironmentConfig, "area" | "density" | "speed" | "dropLength" | "wind" | "color">>;
@@ -123,13 +167,13 @@ export type GrassEnvironmentDescriptor = {
123
167
  } & Required<Pick<GrassEnvironmentConfig, "area" | "density" | "bladeHeight" | "bladeWidth" | "windStrength" | "colors">> & Pick<GrassEnvironmentConfig, "seed">;
124
168
  export type OceanEnvironmentDescriptor = {
125
169
  kind: "ocean";
126
- } & Required<Pick<OceanEnvironmentConfig, "bounds" | "level" | "waveHeight" | "waveScale" | "waveSpeed" | "color">> & Pick<OceanEnvironmentConfig, "position">;
170
+ } & Required<Pick<OceanEnvironmentConfig, "bounds" | "level" | "waveHeight" | "waveScale" | "waveSpeed" | "color">> & Pick<OceanEnvironmentConfig, "position" | "levelAt">;
127
171
  export type BuildingEnvironmentDescriptor = {
128
172
  kind: "building";
129
- } & Required<Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style">> & Pick<BuildingEnvironmentConfig, "seed" | "position">;
173
+ } & Required<Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style">> & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette">;
130
174
  export type PadEnvironmentDescriptor = {
131
175
  kind: "pad";
132
- } & Required<Pick<PadEnvironmentConfig, "center" | "size" | "height" | "color">> & Pick<PadEnvironmentConfig, "rotationY">;
176
+ } & Required<Pick<PadEnvironmentConfig, "center" | "size" | "height" | "color">> & Pick<PadEnvironmentConfig, "rotationY" | "elevation">;
133
177
  export type SkyEnvironmentDescriptor = {
134
178
  kind: "sky";
135
179
  } & Required<Pick<SkyEnvironmentConfig, "preset" | "timeOfDay">> & Omit<SkyEnvironmentConfig, "preset" | "timeOfDay">;
@@ -140,6 +184,8 @@ export type StructureEnvironmentDescriptor = BuildingEnvironmentDescriptor;
140
184
  export type EnvironmentDescriptorList<T> = T | readonly T[];
141
185
  export interface EnvironmentWorldConfig {
142
186
  terrain?: TerrainEnvironmentDescriptor;
187
+ /** Disconnected landmasses at independent altitudes over the base `terrain` (or over void without one) — see `island()`. */
188
+ islands?: readonly TerrainIslandDescriptor[];
143
189
  sky?: SkyEnvironmentDescriptor;
144
190
  weather?: EnvironmentDescriptorList<WeatherEnvironmentDescriptor>;
145
191
  vegetation?: EnvironmentDescriptorList<VegetationEnvironmentDescriptor>;
@@ -151,6 +197,7 @@ export interface EnvironmentWorldConfig {
151
197
  export interface EnvironmentWorldFeature {
152
198
  kind: "environment";
153
199
  terrain?: TerrainEnvironmentDescriptor;
200
+ islands?: readonly TerrainIslandDescriptor[];
154
201
  sky?: SkyEnvironmentDescriptor;
155
202
  weather?: readonly WeatherEnvironmentDescriptor[];
156
203
  vegetation?: readonly VegetationEnvironmentDescriptor[];
@@ -206,7 +253,7 @@ export type WorldFeature = ({
206
253
  } & TilemapWorldConfig) | EnvironmentWorldFeature | {
207
254
  kind: "flat";
208
255
  };
209
- /** Derives implicit `TerrainFlattenMask`s carving each pad's footprint into the terrain beneath it. */
256
+ /** Derives implicit `TerrainFlattenMask`s carving each pad's footprint into the terrain beneath it. Elevated pads (absolute `elevation`) float free and carve nothing. */
210
257
  export declare function padFlattenMasks(pads: readonly PadEnvironmentDescriptor[]): readonly TerrainFlattenMask[];
211
258
  export declare function environment(config?: EnvironmentWorldConfig): EnvironmentWorldFeature;
212
259
  export declare function terrain(config?: TerrainEnvironmentConfig): TerrainEnvironmentDescriptor;
@@ -215,6 +262,7 @@ export declare function rain(config?: RainEnvironmentConfig): RainEnvironmentDes
215
262
  export declare function snow(config?: SnowEnvironmentConfig): SnowEnvironmentDescriptor;
216
263
  export declare function grass(config?: GrassEnvironmentConfig): GrassEnvironmentDescriptor;
217
264
  export declare function ocean(config?: OceanEnvironmentConfig): OceanEnvironmentDescriptor;
265
+ export declare function island(config: TerrainIslandConfig): TerrainIslandDescriptor;
218
266
  export declare function building(config?: BuildingEnvironmentConfig): BuildingEnvironmentDescriptor;
219
267
  export declare function pad(config: PadEnvironmentConfig): PadEnvironmentDescriptor;
220
268
  export declare function biomes(config: BiomesWorldConfig): WorldFeature;