@adatechnology/game-weather 0.0.0-stage → 0.1.1
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.
- package/LICENSE +21 -0
- package/README.md +94 -2
- package/dist/index.d.ts +376 -0
- package/dist/index.global.js +1131 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +1106 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -3
|
@@ -0,0 +1,1131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var GameWeather = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
CLIMATES: () => CLIMATES,
|
|
25
|
+
DAY_REAL_SECONDS: () => DAY_REAL_SECONDS,
|
|
26
|
+
DEFAULT_DRY_OUT_SECONDS: () => DEFAULT_DRY_OUT_SECONDS,
|
|
27
|
+
DEFAULT_START_HOUR: () => DEFAULT_START_HOUR,
|
|
28
|
+
DEFAULT_TIME_SCALE: () => DEFAULT_TIME_SCALE,
|
|
29
|
+
DEFAULT_WET_RISE_SECONDS: () => DEFAULT_WET_RISE_SECONDS,
|
|
30
|
+
DRY_VISIBILITY_METERS: () => DRY_VISIBILITY_METERS,
|
|
31
|
+
FLASH_END_SECONDS: () => FLASH_END_SECONDS,
|
|
32
|
+
GAME_DAY_SECONDS: () => GAME_DAY_SECONDS,
|
|
33
|
+
NIGHT_SIGHT_LOSS: () => NIGHT_SIGHT_LOSS,
|
|
34
|
+
SPEED_OF_SOUND: () => SPEED_OF_SOUND,
|
|
35
|
+
STATE_VALUE_KEYS: () => STATE_VALUE_KEYS,
|
|
36
|
+
WEATHER_STATES: () => WEATHER_STATES,
|
|
37
|
+
blendValues: () => blendValues,
|
|
38
|
+
createLightningStream: () => createLightningStream,
|
|
39
|
+
createRng: () => createRng,
|
|
40
|
+
createScheduler: () => createScheduler,
|
|
41
|
+
createStream: () => createStream,
|
|
42
|
+
createTimeline: () => createTimeline,
|
|
43
|
+
createWeather: () => createWeather,
|
|
44
|
+
createWeatherRegistry: () => createWeatherRegistry,
|
|
45
|
+
createWeatherTestModule: () => createWeatherTestModule,
|
|
46
|
+
createWetness: () => createWetness,
|
|
47
|
+
dayFractionAt: () => dayFractionAt,
|
|
48
|
+
daylight: () => daylight,
|
|
49
|
+
deriveEffects: () => deriveEffects,
|
|
50
|
+
flashIntensity: () => flashIntensity,
|
|
51
|
+
heatIndexC: () => heatIndexC,
|
|
52
|
+
heatStress: () => heatStress,
|
|
53
|
+
streamSeed: () => streamSeed,
|
|
54
|
+
thunderDelay: () => thunderDelay,
|
|
55
|
+
valuesOf: () => valuesOf,
|
|
56
|
+
weatherBenchTextOf: () => weatherBenchTextOf
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// src/blend.ts
|
|
60
|
+
var STATE_VALUE_KEYS = [
|
|
61
|
+
"rain",
|
|
62
|
+
"drops",
|
|
63
|
+
"sheets",
|
|
64
|
+
"windX",
|
|
65
|
+
"windZ",
|
|
66
|
+
"gust",
|
|
67
|
+
"fogFar",
|
|
68
|
+
"fogDensity",
|
|
69
|
+
"skyScale",
|
|
70
|
+
"visibility",
|
|
71
|
+
"humidity",
|
|
72
|
+
"temperatureDeltaC",
|
|
73
|
+
"wetness",
|
|
74
|
+
"hearingScale",
|
|
75
|
+
"footstepScale",
|
|
76
|
+
"dryingScale",
|
|
77
|
+
"debrisPush",
|
|
78
|
+
"haze",
|
|
79
|
+
"mistPerSecond",
|
|
80
|
+
"ashPerSecond"
|
|
81
|
+
];
|
|
82
|
+
function valuesOf(state) {
|
|
83
|
+
const values = {};
|
|
84
|
+
for (const key of STATE_VALUE_KEYS) values[key] = state[key];
|
|
85
|
+
return values;
|
|
86
|
+
}
|
|
87
|
+
function blendValues(from, to, progress) {
|
|
88
|
+
const amount = Math.min(1, Math.max(0, progress));
|
|
89
|
+
const blended = {};
|
|
90
|
+
for (const key of STATE_VALUE_KEYS) blended[key] = from[key] + (to[key] - from[key]) * amount;
|
|
91
|
+
return blended;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/daylight.ts
|
|
95
|
+
var DAY_REAL_SECONDS = 1200;
|
|
96
|
+
var GAME_DAY_SECONDS = 86400;
|
|
97
|
+
var DEFAULT_TIME_SCALE = GAME_DAY_SECONDS / DAY_REAL_SECONDS;
|
|
98
|
+
var NIGHT_SPAN = 2.2;
|
|
99
|
+
var clamp = (value, low, high) => value < low ? low : value > high ? high : value;
|
|
100
|
+
function daylight(dayFraction) {
|
|
101
|
+
const sun = clamp(Math.sin(dayFraction * Math.PI * 2 - Math.PI / 2) * 0.5 + 0.5, 0, 1);
|
|
102
|
+
return { sun, nightFactor: 1 - clamp(sun * NIGHT_SPAN, 0, 1) };
|
|
103
|
+
}
|
|
104
|
+
function dayFractionAt(startHour, gameSeconds) {
|
|
105
|
+
const fraction = (startHour / 24 + gameSeconds / GAME_DAY_SECONDS) % 1;
|
|
106
|
+
return fraction < 0 ? fraction + 1 : fraction;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/heat.ts
|
|
110
|
+
var HEAT_STRESS_BANDS = [
|
|
111
|
+
[27, 0],
|
|
112
|
+
[32, 1 / 3],
|
|
113
|
+
[41, 2 / 3],
|
|
114
|
+
[54, 1]
|
|
115
|
+
];
|
|
116
|
+
var NOAA_REGRESSION_FROM_F = 80;
|
|
117
|
+
var toFahrenheit = (celsius) => celsius * 9 / 5 + 32;
|
|
118
|
+
var toCelsius = (fahrenheit) => (fahrenheit - 32) * 5 / 9;
|
|
119
|
+
function heatIndexC(temperatureC, relativeHumidityPercent) {
|
|
120
|
+
const t = toFahrenheit(temperatureC);
|
|
121
|
+
const r = relativeHumidityPercent;
|
|
122
|
+
const steadman = 0.5 * (t + 61 + (t - 68) * 1.2 + r * 0.094);
|
|
123
|
+
const simple = (steadman + t) / 2;
|
|
124
|
+
if (simple < NOAA_REGRESSION_FROM_F) return toCelsius(simple);
|
|
125
|
+
let index = -42.379 + 2.04901523 * t + 10.14333127 * r - 0.22475541 * t * r - 683783e-8 * t * t - 0.05481717 * r * r + 122874e-8 * t * t * r + 85282e-8 * t * r * r - 199e-8 * t * t * r * r;
|
|
126
|
+
if (r < 13 && t >= 80 && t <= 112) index -= (13 - r) / 4 * Math.sqrt((17 - Math.abs(t - 95)) / 17);
|
|
127
|
+
else if (r > 85 && t >= 80 && t <= 87) index += (r - 85) / 10 * ((87 - t) / 5);
|
|
128
|
+
return toCelsius(index);
|
|
129
|
+
}
|
|
130
|
+
function heatStress(heatIndex) {
|
|
131
|
+
const first = HEAT_STRESS_BANDS[0];
|
|
132
|
+
const last = HEAT_STRESS_BANDS[HEAT_STRESS_BANDS.length - 1];
|
|
133
|
+
if (heatIndex <= first[0]) return first[1];
|
|
134
|
+
if (heatIndex >= last[0]) return last[1];
|
|
135
|
+
for (let index = 1; index < HEAT_STRESS_BANDS.length; index++) {
|
|
136
|
+
const [upperC, upperStress] = HEAT_STRESS_BANDS[index];
|
|
137
|
+
const [lowerC, lowerStress] = HEAT_STRESS_BANDS[index - 1];
|
|
138
|
+
if (heatIndex <= upperC) return lowerStress + (heatIndex - lowerC) / (upperC - lowerC) * (upperStress - lowerStress);
|
|
139
|
+
}
|
|
140
|
+
return last[1];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/effects.ts
|
|
144
|
+
var DRY_VISIBILITY_METERS = 220;
|
|
145
|
+
var NIGHT_SIGHT_LOSS = 0.4;
|
|
146
|
+
function deriveEffects(input) {
|
|
147
|
+
const { values, nightFactor } = input;
|
|
148
|
+
const weatherSight = Math.min(1, Math.max(0, values.visibility / DRY_VISIBILITY_METERS));
|
|
149
|
+
const heatIndex = heatIndexC(input.temperatureC, input.humidityPercent);
|
|
150
|
+
return {
|
|
151
|
+
visibility: values.visibility,
|
|
152
|
+
sightScale: weatherSight * (1 - NIGHT_SIGHT_LOSS * nightFactor),
|
|
153
|
+
hearingScale: values.hearingScale,
|
|
154
|
+
footstepScale: values.footstepScale,
|
|
155
|
+
heatIndexC: heatIndex,
|
|
156
|
+
heatStress: heatStress(heatIndex),
|
|
157
|
+
lightningReveal: input.lightningReveal,
|
|
158
|
+
dryingScale: values.dryingScale,
|
|
159
|
+
debrisPush: values.debrisPush
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/rng.ts
|
|
164
|
+
var FNV_OFFSET = 2166136261;
|
|
165
|
+
var FNV_PRIME = 16777619;
|
|
166
|
+
var MIX = 2654435761;
|
|
167
|
+
function createRng(seed) {
|
|
168
|
+
let state = seed >>> 0;
|
|
169
|
+
return () => {
|
|
170
|
+
state = state + 1831565813 >>> 0;
|
|
171
|
+
let mixed = Math.imul(state ^ state >>> 15, state | 1);
|
|
172
|
+
mixed ^= mixed + Math.imul(mixed ^ mixed >>> 7, mixed | 61);
|
|
173
|
+
return ((mixed ^ mixed >>> 14) >>> 0) / 4294967296;
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function streamSeed(seed, name) {
|
|
177
|
+
let hash = FNV_OFFSET;
|
|
178
|
+
for (let index = 0; index < name.length; index++) hash = Math.imul(hash ^ name.charCodeAt(index), FNV_PRIME) >>> 0;
|
|
179
|
+
return Math.imul(seed >>> 0 ^ hash, MIX) >>> 0;
|
|
180
|
+
}
|
|
181
|
+
function createStream(seed, name) {
|
|
182
|
+
return createRng(streamSeed(seed, name));
|
|
183
|
+
}
|
|
184
|
+
function drawBetween(rng, range) {
|
|
185
|
+
return range[0] + rng() * (range[1] - range[0]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/lightning.ts
|
|
189
|
+
var SPEED_OF_SOUND = 343;
|
|
190
|
+
var FLASH_END_SECONDS = 0.4;
|
|
191
|
+
function thunderDelay(distance) {
|
|
192
|
+
return distance / SPEED_OF_SOUND;
|
|
193
|
+
}
|
|
194
|
+
function flashIntensity(elapsed) {
|
|
195
|
+
if (elapsed < 0 || elapsed >= FLASH_END_SECONDS) return 0;
|
|
196
|
+
if (elapsed < 0.08) return 1;
|
|
197
|
+
if (elapsed < 0.16) return 0.2;
|
|
198
|
+
if (elapsed < 0.26) return 0.8;
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
function createLightningStream(seed) {
|
|
202
|
+
const rng = createStream(seed, "lightning");
|
|
203
|
+
return {
|
|
204
|
+
first: (config) => drawBetween(rng, config.first),
|
|
205
|
+
interval: (config) => drawBetween(rng, config.interval),
|
|
206
|
+
place(config) {
|
|
207
|
+
const distance = drawBetween(rng, config.distance);
|
|
208
|
+
return { distance, bearing: rng() * 360 };
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/data/climates.json
|
|
214
|
+
var climates_default = [
|
|
215
|
+
{
|
|
216
|
+
id: "temperate",
|
|
217
|
+
sources: ["galeria-vale-cinzento:L1393 (changes every 2-6 h, arrives from the west)", "design:T6 3.2 kit default: mostly clear and overcast, no extremes"],
|
|
218
|
+
start: "clear",
|
|
219
|
+
dwellHours: [2, 6],
|
|
220
|
+
blendMinutes: 10,
|
|
221
|
+
frontDirection: [1, 0],
|
|
222
|
+
temperatureC: { mean: 16, swing: 6 },
|
|
223
|
+
humidityOffset: 0,
|
|
224
|
+
transitions: {
|
|
225
|
+
clear: { overcast: 4, windy: 1, fog: 1, light_rain: 1 },
|
|
226
|
+
overcast: { clear: 3, light_rain: 3, heavy_rain: 1, fog: 1, windy: 1 },
|
|
227
|
+
light_rain: { overcast: 3, clear: 2, heavy_rain: 2 },
|
|
228
|
+
heavy_rain: { light_rain: 3, overcast: 2, storm: 1 },
|
|
229
|
+
storm: { heavy_rain: 3, light_rain: 2, overcast: 1 },
|
|
230
|
+
fog: { clear: 3, overcast: 2 },
|
|
231
|
+
windy: { clear: 3, overcast: 3, light_rain: 1 }
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
id: "tropical",
|
|
236
|
+
sources: ["galeria-vale-cinzento:L1393 (changes every 2-6 h, arrives from the west)", "design:T6 3.2 and Q3: extremes rare and enabled"],
|
|
237
|
+
start: "clear",
|
|
238
|
+
dwellHours: [2, 6],
|
|
239
|
+
blendMinutes: 10,
|
|
240
|
+
frontDirection: [1, 0],
|
|
241
|
+
temperatureC: { mean: 29, swing: 3 },
|
|
242
|
+
humidityOffset: 0.1,
|
|
243
|
+
transitions: {
|
|
244
|
+
clear: { overcast: 3, light_rain: 2, windy: 1, extreme_heat: 0.8 },
|
|
245
|
+
overcast: { clear: 3, light_rain: 3, heavy_rain: 2, fog: 0.5 },
|
|
246
|
+
light_rain: { overcast: 3, clear: 2, heavy_rain: 2 },
|
|
247
|
+
heavy_rain: { light_rain: 3, overcast: 2, storm: 1.5 },
|
|
248
|
+
storm: { heavy_rain: 3, light_rain: 2, overcast: 1, cyclone: 0.5 },
|
|
249
|
+
fog: { clear: 3, overcast: 2 },
|
|
250
|
+
windy: { clear: 3, overcast: 2, light_rain: 1, storm: 0.5 },
|
|
251
|
+
extreme_heat: { clear: 4, overcast: 2, storm: 0.8 },
|
|
252
|
+
cyclone: { storm: 3, heavy_rain: 2, typhoon: 0.5 },
|
|
253
|
+
typhoon: { cyclone: 3, storm: 2 }
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
// src/data/states.json
|
|
259
|
+
var states_default = [
|
|
260
|
+
{
|
|
261
|
+
id: "clear",
|
|
262
|
+
sources: ["jogo:L69141 (fog far 220 dry)", "showroom.js:L1693-1710 (breeze 3.5, 1.5 m/s)"],
|
|
263
|
+
rain: 0,
|
|
264
|
+
drops: 0,
|
|
265
|
+
sheets: 0,
|
|
266
|
+
windX: 3.5,
|
|
267
|
+
windZ: 1.5,
|
|
268
|
+
gust: 5,
|
|
269
|
+
fogFar: 220,
|
|
270
|
+
fogDensity: 0,
|
|
271
|
+
skyScale: 1,
|
|
272
|
+
visibility: 220,
|
|
273
|
+
humidity: 0.5,
|
|
274
|
+
temperatureDeltaC: 0,
|
|
275
|
+
wetness: 0,
|
|
276
|
+
hearingScale: 1,
|
|
277
|
+
footstepScale: 1,
|
|
278
|
+
dryingScale: 1,
|
|
279
|
+
debrisPush: 0,
|
|
280
|
+
haze: 0,
|
|
281
|
+
mistPerSecond: 0,
|
|
282
|
+
ashPerSecond: 0
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: "overcast",
|
|
286
|
+
sources: ["cena-de-arte:L413 (wind .35, .12)", "cena-de-arte:L36 (FogExp2 .03)", "design:T6 sky x0.9"],
|
|
287
|
+
rain: 0,
|
|
288
|
+
drops: 0,
|
|
289
|
+
sheets: 0,
|
|
290
|
+
windX: 0.35,
|
|
291
|
+
windZ: 0.12,
|
|
292
|
+
gust: 0.6,
|
|
293
|
+
fogFar: 220,
|
|
294
|
+
fogDensity: 0.03,
|
|
295
|
+
skyScale: 0.9,
|
|
296
|
+
visibility: 200,
|
|
297
|
+
humidity: 0.65,
|
|
298
|
+
temperatureDeltaC: -1,
|
|
299
|
+
wetness: 0,
|
|
300
|
+
hearingScale: 1,
|
|
301
|
+
footstepScale: 1,
|
|
302
|
+
dryingScale: 1,
|
|
303
|
+
debrisPush: 0,
|
|
304
|
+
haze: 0,
|
|
305
|
+
mistPerSecond: 0,
|
|
306
|
+
ashPerSecond: 0
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
id: "light_rain",
|
|
310
|
+
sources: ["oficina-vfx:W1065 (Garoa: 900 drops, slant .3)", "jogo:L70416-70427 (rain level .035)", "design:T6 2.5 mm/h, wetness .35, visibility 160, hearing x0.8 (Q4)"],
|
|
311
|
+
rain: 2.5,
|
|
312
|
+
drops: 900,
|
|
313
|
+
sheets: 0,
|
|
314
|
+
windX: 0.3,
|
|
315
|
+
windZ: 0,
|
|
316
|
+
gust: 0.6,
|
|
317
|
+
fogFar: 180,
|
|
318
|
+
fogDensity: 0,
|
|
319
|
+
skyScale: 0.9,
|
|
320
|
+
visibility: 160,
|
|
321
|
+
humidity: 0.85,
|
|
322
|
+
temperatureDeltaC: -2,
|
|
323
|
+
wetness: 0.35,
|
|
324
|
+
hearingScale: 0.8,
|
|
325
|
+
footstepScale: 0.85,
|
|
326
|
+
dryingScale: 1,
|
|
327
|
+
debrisPush: 0,
|
|
328
|
+
haze: 0,
|
|
329
|
+
mistPerSecond: 0,
|
|
330
|
+
ashPerSecond: 0
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
id: "heavy_rain",
|
|
334
|
+
sources: ["oficina-vfx:W1065 (Chuva: 2200 drops, slant 1.2)", "cena-de-arte:L1314 (rain gain .06)", "jogo:L69141 (fog far 140, sky x0.8)", "galeria-vale-cinzento:L1393 (vision 120 m, quieter steps)", "design:T6 15 mm/h, hearing x0.6 (Q4)"],
|
|
335
|
+
rain: 15,
|
|
336
|
+
drops: 2200,
|
|
337
|
+
sheets: 0,
|
|
338
|
+
windX: 1.2,
|
|
339
|
+
windZ: 0,
|
|
340
|
+
gust: 2,
|
|
341
|
+
fogFar: 140,
|
|
342
|
+
fogDensity: 0,
|
|
343
|
+
skyScale: 0.8,
|
|
344
|
+
visibility: 120,
|
|
345
|
+
humidity: 0.9,
|
|
346
|
+
temperatureDeltaC: -4,
|
|
347
|
+
wetness: 0.8,
|
|
348
|
+
hearingScale: 0.6,
|
|
349
|
+
footstepScale: 0.7,
|
|
350
|
+
dryingScale: 1,
|
|
351
|
+
debrisPush: 0,
|
|
352
|
+
haze: 0,
|
|
353
|
+
mistPerSecond: 0,
|
|
354
|
+
ashPerSecond: 0
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
id: "storm",
|
|
358
|
+
sources: ["oficina-vfx:W1065-1069 (Temporal: 4000 drops, slant 4.5, lightning 3.5-6 s, first at 2-4 s, sheets)", "showroom.js (60 mm/h)", "design:T6 hearing x0.5 (Q4)"],
|
|
359
|
+
rain: 60,
|
|
360
|
+
drops: 4e3,
|
|
361
|
+
sheets: 1,
|
|
362
|
+
windX: 4.5,
|
|
363
|
+
windZ: 0,
|
|
364
|
+
gust: 6,
|
|
365
|
+
fogFar: 110,
|
|
366
|
+
fogDensity: 0,
|
|
367
|
+
skyScale: 0.7,
|
|
368
|
+
visibility: 100,
|
|
369
|
+
humidity: 0.92,
|
|
370
|
+
temperatureDeltaC: -5,
|
|
371
|
+
wetness: 1,
|
|
372
|
+
hearingScale: 0.5,
|
|
373
|
+
footstepScale: 0.6,
|
|
374
|
+
dryingScale: 1,
|
|
375
|
+
debrisPush: 0,
|
|
376
|
+
haze: 0,
|
|
377
|
+
mistPerSecond: 0,
|
|
378
|
+
ashPerSecond: 0,
|
|
379
|
+
lightning: { interval: [3.5, 6], first: [2, 4], distance: [200, 4e3], reveal: 0.2 }
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
id: "fog",
|
|
383
|
+
sources: ["oficina-vfx:W1073-1080 (density .075, ground mist 5/s, ash 14/s)", "jogo:L69101 (zone fog far 60)"],
|
|
384
|
+
rain: 0,
|
|
385
|
+
drops: 0,
|
|
386
|
+
sheets: 0,
|
|
387
|
+
windX: 0.3,
|
|
388
|
+
windZ: 0.1,
|
|
389
|
+
gust: 0.5,
|
|
390
|
+
fogFar: 60,
|
|
391
|
+
fogDensity: 0.075,
|
|
392
|
+
skyScale: 0.85,
|
|
393
|
+
visibility: 40,
|
|
394
|
+
humidity: 0.95,
|
|
395
|
+
temperatureDeltaC: -3,
|
|
396
|
+
wetness: 0.15,
|
|
397
|
+
hearingScale: 1,
|
|
398
|
+
footstepScale: 1,
|
|
399
|
+
dryingScale: 1,
|
|
400
|
+
debrisPush: 0,
|
|
401
|
+
haze: 0,
|
|
402
|
+
mistPerSecond: 5,
|
|
403
|
+
ashPerSecond: 14
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: "windy",
|
|
407
|
+
sources: ["cena-de-arte:L1315 (wind loop)", "design:T6 Q3 8 m/s, gust 12"],
|
|
408
|
+
rain: 0,
|
|
409
|
+
drops: 0,
|
|
410
|
+
sheets: 0,
|
|
411
|
+
windX: 8,
|
|
412
|
+
windZ: 0,
|
|
413
|
+
gust: 12,
|
|
414
|
+
fogFar: 220,
|
|
415
|
+
fogDensity: 0,
|
|
416
|
+
skyScale: 1,
|
|
417
|
+
visibility: 220,
|
|
418
|
+
humidity: 0.45,
|
|
419
|
+
temperatureDeltaC: -1,
|
|
420
|
+
wetness: 0,
|
|
421
|
+
hearingScale: 0.85,
|
|
422
|
+
footstepScale: 0.9,
|
|
423
|
+
dryingScale: 1,
|
|
424
|
+
debrisPush: 0,
|
|
425
|
+
haze: 0,
|
|
426
|
+
mistPerSecond: 0,
|
|
427
|
+
ashPerSecond: 0
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
id: "cyclone",
|
|
431
|
+
sources: ["design:T6 Q3 physics preset, no team source: tropical cyclone 33 m/s sustained, gust x1.3, storm rain, debris push"],
|
|
432
|
+
rain: 60,
|
|
433
|
+
drops: 4e3,
|
|
434
|
+
sheets: 1,
|
|
435
|
+
windX: 33,
|
|
436
|
+
windZ: 0,
|
|
437
|
+
gust: 42.9,
|
|
438
|
+
fogFar: 80,
|
|
439
|
+
fogDensity: 0,
|
|
440
|
+
skyScale: 0.6,
|
|
441
|
+
visibility: 60,
|
|
442
|
+
humidity: 0.9,
|
|
443
|
+
temperatureDeltaC: 0,
|
|
444
|
+
wetness: 1,
|
|
445
|
+
hearingScale: 0.3,
|
|
446
|
+
footstepScale: 0.5,
|
|
447
|
+
dryingScale: 1,
|
|
448
|
+
debrisPush: 8,
|
|
449
|
+
haze: 0,
|
|
450
|
+
mistPerSecond: 0,
|
|
451
|
+
ashPerSecond: 0,
|
|
452
|
+
lightning: { interval: [3.5, 6], first: [2, 4], distance: [200, 4e3], reveal: 0.2 }
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
id: "typhoon",
|
|
456
|
+
sources: ["design:T6 Q3 physics preset, no team source: typhoon 50 m/s sustained, gust x1.3, storm rain, debris push"],
|
|
457
|
+
rain: 60,
|
|
458
|
+
drops: 4e3,
|
|
459
|
+
sheets: 1,
|
|
460
|
+
windX: 50,
|
|
461
|
+
windZ: 0,
|
|
462
|
+
gust: 65,
|
|
463
|
+
fogFar: 60,
|
|
464
|
+
fogDensity: 0,
|
|
465
|
+
skyScale: 0.5,
|
|
466
|
+
visibility: 40,
|
|
467
|
+
humidity: 0.9,
|
|
468
|
+
temperatureDeltaC: 0,
|
|
469
|
+
wetness: 1,
|
|
470
|
+
hearingScale: 0.25,
|
|
471
|
+
footstepScale: 0.4,
|
|
472
|
+
dryingScale: 1,
|
|
473
|
+
debrisPush: 18,
|
|
474
|
+
haze: 0,
|
|
475
|
+
mistPerSecond: 0,
|
|
476
|
+
ashPerSecond: 0,
|
|
477
|
+
lightning: { interval: [3.5, 6], first: [2, 4], distance: [200, 4e3], reveal: 0.2 }
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
id: "extreme_heat",
|
|
481
|
+
sources: ["design:T6 Q3 no team source: 38 C at the tropical noon (+6 over the climate), 60 % humidity, haze, drying x2"],
|
|
482
|
+
rain: 0,
|
|
483
|
+
drops: 0,
|
|
484
|
+
sheets: 0,
|
|
485
|
+
windX: 0.8,
|
|
486
|
+
windZ: 0.3,
|
|
487
|
+
gust: 1.5,
|
|
488
|
+
fogFar: 220,
|
|
489
|
+
fogDensity: 0,
|
|
490
|
+
skyScale: 1,
|
|
491
|
+
visibility: 200,
|
|
492
|
+
humidity: 0.6,
|
|
493
|
+
temperatureDeltaC: 6,
|
|
494
|
+
wetness: 0,
|
|
495
|
+
hearingScale: 1,
|
|
496
|
+
footstepScale: 1,
|
|
497
|
+
dryingScale: 2,
|
|
498
|
+
debrisPush: 0,
|
|
499
|
+
haze: 1,
|
|
500
|
+
mistPerSecond: 0,
|
|
501
|
+
ashPerSecond: 0
|
|
502
|
+
}
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
// src/registry.ts
|
|
506
|
+
var WEATHER_STATES = states_default;
|
|
507
|
+
var CLIMATES = climates_default;
|
|
508
|
+
function isPlainObject(value) {
|
|
509
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
510
|
+
}
|
|
511
|
+
function mergeRecords(base, patch) {
|
|
512
|
+
const merged = { ...base };
|
|
513
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
514
|
+
if (value === void 0) continue;
|
|
515
|
+
const current = merged[key];
|
|
516
|
+
merged[key] = isPlainObject(current) && isPlainObject(value) ? mergeRecords(current, value) : value;
|
|
517
|
+
}
|
|
518
|
+
return merged;
|
|
519
|
+
}
|
|
520
|
+
function deepMerge(base, patch) {
|
|
521
|
+
return mergeRecords(base, patch);
|
|
522
|
+
}
|
|
523
|
+
function createTable(shipped) {
|
|
524
|
+
return new Map(shipped.map((entry) => [entry.id, entry]));
|
|
525
|
+
}
|
|
526
|
+
function patchEntry(table, id, patch) {
|
|
527
|
+
const entry = table.get(id);
|
|
528
|
+
if (entry) table.set(id, deepMerge(entry, patch));
|
|
529
|
+
}
|
|
530
|
+
function createWeatherRegistry(options = {}) {
|
|
531
|
+
const states = createTable(WEATHER_STATES);
|
|
532
|
+
const climates = createTable(CLIMATES);
|
|
533
|
+
for (const state of options.states ?? []) states.set(state.id, state);
|
|
534
|
+
for (const climate of options.climates ?? []) climates.set(climate.id, climate);
|
|
535
|
+
for (const [id, patch] of Object.entries(options.patch?.states ?? {})) patchEntry(states, id, patch);
|
|
536
|
+
for (const [id, patch] of Object.entries(options.patch?.climates ?? {})) patchEntry(climates, id, patch);
|
|
537
|
+
for (const id of options.remove?.states ?? []) states.delete(id);
|
|
538
|
+
for (const id of options.remove?.climates ?? []) climates.delete(id);
|
|
539
|
+
return {
|
|
540
|
+
state: (id) => states.get(id),
|
|
541
|
+
climate: (id) => climates.get(id),
|
|
542
|
+
stateIds: () => [...states.keys()],
|
|
543
|
+
climateIds: () => [...climates.keys()],
|
|
544
|
+
registerState: (state) => void states.set(state.id, state),
|
|
545
|
+
registerClimate: (climate) => void climates.set(climate.id, climate),
|
|
546
|
+
patchState: (id, patch) => patchEntry(states, id, patch),
|
|
547
|
+
patchClimate: (id, patch) => patchEntry(climates, id, patch),
|
|
548
|
+
removeState: (id) => void states.delete(id),
|
|
549
|
+
removeClimate: (id) => void climates.delete(id)
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// src/scheduler.ts
|
|
554
|
+
var SECONDS_PER_HOUR = 3600;
|
|
555
|
+
var MIN_DWELL_SECONDS = 1;
|
|
556
|
+
function createScheduler(climate, seed, isKnown) {
|
|
557
|
+
const rng = createStream(seed, "schedule");
|
|
558
|
+
let changeAt = 0;
|
|
559
|
+
const drawDwell = (from) => from + Math.max(MIN_DWELL_SECONDS, drawBetween(rng, climate.dwellHours) * SECONDS_PER_HOUR);
|
|
560
|
+
changeAt = drawDwell(0);
|
|
561
|
+
return {
|
|
562
|
+
nextChangeAt: () => changeAt,
|
|
563
|
+
next(current, at) {
|
|
564
|
+
const options = Object.entries(climate.transitions[current] ?? {}).filter(([id, weight]) => weight > 0 && isKnown(id));
|
|
565
|
+
const total = options.reduce((sum, [, weight]) => sum + weight, 0);
|
|
566
|
+
let chosen = current;
|
|
567
|
+
if (total > 0) {
|
|
568
|
+
let pick = rng() * total;
|
|
569
|
+
chosen = options[options.length - 1][0];
|
|
570
|
+
for (const [id, weight] of options) {
|
|
571
|
+
if (pick < weight) {
|
|
572
|
+
chosen = id;
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
pick -= weight;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
changeAt = drawDwell(at);
|
|
579
|
+
return chosen;
|
|
580
|
+
},
|
|
581
|
+
restart(at) {
|
|
582
|
+
changeAt = drawDwell(at);
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/wetness.ts
|
|
588
|
+
var DEFAULT_WET_RISE_SECONDS = 120;
|
|
589
|
+
var DEFAULT_DRY_OUT_SECONDS = 180;
|
|
590
|
+
function createWetness(options = {}) {
|
|
591
|
+
const riseSeconds = options.riseSeconds ?? DEFAULT_WET_RISE_SECONDS;
|
|
592
|
+
const dryOutSeconds = options.dryOutSeconds ?? DEFAULT_DRY_OUT_SECONDS;
|
|
593
|
+
let current = 0;
|
|
594
|
+
let peak = 0;
|
|
595
|
+
let isDrying = false;
|
|
596
|
+
return {
|
|
597
|
+
value: () => current,
|
|
598
|
+
update(seconds, target, dryingScale = 1) {
|
|
599
|
+
if (target > current) {
|
|
600
|
+
isDrying = false;
|
|
601
|
+
current = Math.min(target, current + seconds / riseSeconds);
|
|
602
|
+
} else if (target < current) {
|
|
603
|
+
if (!isDrying) {
|
|
604
|
+
isDrying = true;
|
|
605
|
+
peak = current;
|
|
606
|
+
}
|
|
607
|
+
current = Math.max(target, current - peak * seconds * dryingScale / dryOutSeconds);
|
|
608
|
+
} else isDrying = false;
|
|
609
|
+
return current;
|
|
610
|
+
},
|
|
611
|
+
reset(value = 0) {
|
|
612
|
+
current = value;
|
|
613
|
+
peak = value;
|
|
614
|
+
isDrying = false;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// src/weather.ts
|
|
620
|
+
var DEFAULT_START_HOUR = 8;
|
|
621
|
+
var SECONDS_PER_MINUTE = 60;
|
|
622
|
+
var WETNESS_STEP_SECONDS = 1;
|
|
623
|
+
var clamp01 = (value) => Math.min(1, Math.max(0, value));
|
|
624
|
+
function requireClimate(registry, id) {
|
|
625
|
+
const climate = registry.climate(id);
|
|
626
|
+
if (!climate) throw new Error(`game-weather: unknown climate "${id}"`);
|
|
627
|
+
return climate;
|
|
628
|
+
}
|
|
629
|
+
function requireState(registry, id) {
|
|
630
|
+
const state = registry.state(id);
|
|
631
|
+
if (!state) throw new Error(`game-weather: unknown state "${id}"`);
|
|
632
|
+
return state;
|
|
633
|
+
}
|
|
634
|
+
function createWeather(options = {}) {
|
|
635
|
+
const registry = options.registry ?? createWeatherRegistry(options);
|
|
636
|
+
const seed = options.seed ?? 1;
|
|
637
|
+
const climateId = options.climate ?? "temperate";
|
|
638
|
+
const startClimate = requireClimate(registry, climateId);
|
|
639
|
+
const startState = requireState(registry, options.start ?? startClimate.start);
|
|
640
|
+
const startHour = options.startHour ?? DEFAULT_START_HOUR;
|
|
641
|
+
const scheduler = createScheduler(startClimate, seed, (id) => registry.state(id) !== void 0);
|
|
642
|
+
const lightning = createLightningStream(seed);
|
|
643
|
+
const wetness = createWetness(options.wetness);
|
|
644
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
645
|
+
let timeScale = options.timeScale ?? DEFAULT_TIME_SCALE;
|
|
646
|
+
let isAutoSchedule = options.autoSchedule ?? true;
|
|
647
|
+
let gameSeconds = 0;
|
|
648
|
+
let simSeconds = 0;
|
|
649
|
+
let target = startState;
|
|
650
|
+
let targetValues = valuesOf(startState);
|
|
651
|
+
let fromValues = targetValues;
|
|
652
|
+
let previousId;
|
|
653
|
+
let blend = { startedAt: 0, duration: 0 };
|
|
654
|
+
let nextStrikeAt;
|
|
655
|
+
let strikeCount = 0;
|
|
656
|
+
let lastStrike;
|
|
657
|
+
const pendingThunder = [];
|
|
658
|
+
const climate = () => registry.climate(climateId) ?? startClimate;
|
|
659
|
+
const emit = (event, payload) => {
|
|
660
|
+
for (const listener of [...listeners.get(event) ?? []]) listener(payload);
|
|
661
|
+
};
|
|
662
|
+
const progressAt = (at) => blend.duration <= 0 ? 1 : clamp01((at - blend.startedAt) / blend.duration);
|
|
663
|
+
const valuesAt = (at) => blendValues(fromValues, targetValues, progressAt(at));
|
|
664
|
+
const scheduleFirstStrike = (atSim) => {
|
|
665
|
+
nextStrikeAt = target.lightning ? atSim + lightning.first(target.lightning) : void 0;
|
|
666
|
+
};
|
|
667
|
+
const enter = (id, atGame, atSim, blendGameSeconds) => {
|
|
668
|
+
const next = requireState(registry, id);
|
|
669
|
+
fromValues = valuesAt(atGame);
|
|
670
|
+
previousId = target.id;
|
|
671
|
+
target = next;
|
|
672
|
+
targetValues = valuesOf(next);
|
|
673
|
+
blend = { startedAt: atGame, duration: blendGameSeconds };
|
|
674
|
+
scheduleFirstStrike(atSim);
|
|
675
|
+
emit("transition", { from: previousId, to: id, gameSeconds: atGame });
|
|
676
|
+
};
|
|
677
|
+
const strikeAt = (atSim, startGame, startSim) => {
|
|
678
|
+
const config = target.lightning;
|
|
679
|
+
const place = lightning.place(config);
|
|
680
|
+
const strike = {
|
|
681
|
+
id: strikeCount++,
|
|
682
|
+
time: atSim,
|
|
683
|
+
gameSeconds: startGame + (atSim - startSim) * timeScale,
|
|
684
|
+
distance: place.distance,
|
|
685
|
+
bearing: place.bearing,
|
|
686
|
+
thunderDelay: thunderDelay(place.distance)
|
|
687
|
+
};
|
|
688
|
+
lastStrike = strike;
|
|
689
|
+
pendingThunder.push(strike);
|
|
690
|
+
pendingThunder.sort((a, b) => a.time + a.thunderDelay - (b.time + b.thunderDelay));
|
|
691
|
+
nextStrikeAt = atSim + lightning.interval(config);
|
|
692
|
+
emit("strike", strike);
|
|
693
|
+
};
|
|
694
|
+
scheduleFirstStrike(0);
|
|
695
|
+
const runEvents = (startGame, startSim, endSim) => {
|
|
696
|
+
for (; ; ) {
|
|
697
|
+
const changeSim = isAutoSchedule ? Math.max(startSim, startSim + (scheduler.nextChangeAt() - startGame) / timeScale) : Infinity;
|
|
698
|
+
const strikeSim = nextStrikeAt ?? Infinity;
|
|
699
|
+
const thunder = pendingThunder[0];
|
|
700
|
+
const thunderSim = thunder ? thunder.time + thunder.thunderDelay : Infinity;
|
|
701
|
+
const earliest = Math.min(changeSim, strikeSim, thunderSim);
|
|
702
|
+
if (earliest > endSim) return;
|
|
703
|
+
if (earliest === changeSim) {
|
|
704
|
+
const atGame = Math.max(startGame, scheduler.nextChangeAt());
|
|
705
|
+
const next = scheduler.next(target.id, atGame);
|
|
706
|
+
enter(next, atGame, changeSim, climate().blendMinutes * SECONDS_PER_MINUTE);
|
|
707
|
+
} else if (earliest === strikeSim) strikeAt(strikeSim, startGame, startSim);
|
|
708
|
+
else emit("thunder", pendingThunder.shift());
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
const wet = (startGame, endGame, seconds) => {
|
|
712
|
+
const steps = Math.max(1, Math.ceil(seconds / WETNESS_STEP_SECONDS));
|
|
713
|
+
for (let step = 1; step <= steps; step++) {
|
|
714
|
+
const values = valuesAt(startGame + (endGame - startGame) * step / steps);
|
|
715
|
+
wetness.update(seconds / steps, values.wetness, values.dryingScale);
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
const api = {
|
|
719
|
+
registry,
|
|
720
|
+
advance(dt) {
|
|
721
|
+
if (!(dt > 0)) return;
|
|
722
|
+
const startGame = gameSeconds;
|
|
723
|
+
const startSim = simSeconds;
|
|
724
|
+
const endGame = startGame + dt * timeScale;
|
|
725
|
+
runEvents(startGame, startSim, startSim + dt);
|
|
726
|
+
gameSeconds = endGame;
|
|
727
|
+
simSeconds = startSim + dt;
|
|
728
|
+
wet(startGame, endGame, dt);
|
|
729
|
+
},
|
|
730
|
+
sample() {
|
|
731
|
+
const values = valuesAt(gameSeconds);
|
|
732
|
+
const dayFraction = dayFractionAt(startHour, gameSeconds);
|
|
733
|
+
const { sun, nightFactor } = daylight(dayFraction);
|
|
734
|
+
const current = climate();
|
|
735
|
+
const temperatureC = current.temperatureC.mean + current.temperatureC.swing * (2 * sun - 1) + values.temperatureDeltaC;
|
|
736
|
+
const humidityPercent = clamp01(values.humidity + current.humidityOffset) * 100;
|
|
737
|
+
const progress = progressAt(gameSeconds);
|
|
738
|
+
return {
|
|
739
|
+
...values,
|
|
740
|
+
gameSeconds,
|
|
741
|
+
dayFraction,
|
|
742
|
+
hour: dayFraction * 24,
|
|
743
|
+
nightFactor,
|
|
744
|
+
sun,
|
|
745
|
+
state: target.id,
|
|
746
|
+
previous: progress < 1 ? previousId : void 0,
|
|
747
|
+
blend: progress,
|
|
748
|
+
front: { direction: current.frontDirection, progress },
|
|
749
|
+
windSpeed: Math.sqrt(values.windX * values.windX + values.windZ * values.windZ),
|
|
750
|
+
temperatureC,
|
|
751
|
+
humidityPercent,
|
|
752
|
+
groundWetness: wetness.value(),
|
|
753
|
+
flash: lastStrike ? flashIntensity(simSeconds - lastStrike.time) : 0,
|
|
754
|
+
effects: deriveEffects({ values, nightFactor, temperatureC, humidityPercent, lightningReveal: target.lightning?.reveal ?? 0 })
|
|
755
|
+
};
|
|
756
|
+
},
|
|
757
|
+
state: () => target.id,
|
|
758
|
+
force(id, blendSeconds = 0) {
|
|
759
|
+
enter(id, gameSeconds, simSeconds, blendSeconds * timeScale);
|
|
760
|
+
scheduler.restart(gameSeconds);
|
|
761
|
+
},
|
|
762
|
+
setTimeScale(next) {
|
|
763
|
+
if (!(next > 0)) throw new Error("game-weather: timeScale must be above 0");
|
|
764
|
+
timeScale = next;
|
|
765
|
+
},
|
|
766
|
+
setAutoSchedule(isOn) {
|
|
767
|
+
if (isOn && !isAutoSchedule) scheduler.restart(gameSeconds);
|
|
768
|
+
isAutoSchedule = isOn;
|
|
769
|
+
},
|
|
770
|
+
timeline: (hours, sampleMinutes) => createTimeline({ ...options, registry, hours, ...sampleMinutes === void 0 ? {} : { sampleMinutes } }),
|
|
771
|
+
on(event, listener) {
|
|
772
|
+
const set = listeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
773
|
+
set.add(listener);
|
|
774
|
+
listeners.set(event, set);
|
|
775
|
+
return () => void set.delete(listener);
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
return api;
|
|
779
|
+
}
|
|
780
|
+
var TIMELINE_DECIMALS = 6;
|
|
781
|
+
var round = (value) => Math.round(value * 10 ** TIMELINE_DECIMALS) / 10 ** TIMELINE_DECIMALS + 0;
|
|
782
|
+
function createTimeline(options) {
|
|
783
|
+
const weather = createWeather(options);
|
|
784
|
+
const timeScale = options.timeScale ?? DEFAULT_TIME_SCALE;
|
|
785
|
+
const sampleMinutes = options.sampleMinutes ?? 10;
|
|
786
|
+
const transitions = [];
|
|
787
|
+
const strikes = [];
|
|
788
|
+
weather.on("transition", (event) => transitions.push({ gameSeconds: round(event.gameSeconds), from: event.from, to: event.to }));
|
|
789
|
+
weather.on(
|
|
790
|
+
"strike",
|
|
791
|
+
(strike) => strikes.push({ id: strike.id, time: round(strike.time), gameSeconds: round(strike.gameSeconds), distance: round(strike.distance), bearing: round(strike.bearing), thunderDelay: round(strike.thunderDelay) })
|
|
792
|
+
);
|
|
793
|
+
const samples = [];
|
|
794
|
+
const record = (minute) => {
|
|
795
|
+
const sample = weather.sample();
|
|
796
|
+
samples.push({
|
|
797
|
+
at: minute * SECONDS_PER_MINUTE,
|
|
798
|
+
hour: round(sample.hour),
|
|
799
|
+
state: sample.state,
|
|
800
|
+
blend: round(sample.blend),
|
|
801
|
+
rain: round(sample.rain),
|
|
802
|
+
windSpeed: round(sample.windSpeed),
|
|
803
|
+
gust: round(sample.gust),
|
|
804
|
+
visibility: round(sample.effects.visibility),
|
|
805
|
+
sightScale: round(sample.effects.sightScale),
|
|
806
|
+
hearingScale: round(sample.effects.hearingScale),
|
|
807
|
+
temperatureC: round(sample.temperatureC),
|
|
808
|
+
heatIndexC: round(sample.effects.heatIndexC),
|
|
809
|
+
heatStress: round(sample.effects.heatStress),
|
|
810
|
+
wetness: round(sample.groundWetness),
|
|
811
|
+
nightFactor: round(sample.nightFactor)
|
|
812
|
+
});
|
|
813
|
+
};
|
|
814
|
+
record(0);
|
|
815
|
+
const minutes = Math.round(options.hours * 60);
|
|
816
|
+
for (let minute = 1; minute <= minutes; minute++) {
|
|
817
|
+
weather.advance(SECONDS_PER_MINUTE / timeScale);
|
|
818
|
+
if (minute % sampleMinutes === 0) record(minute);
|
|
819
|
+
}
|
|
820
|
+
return {
|
|
821
|
+
seed: options.seed ?? 1,
|
|
822
|
+
climate: options.climate ?? "temperate",
|
|
823
|
+
start: options.start ?? weather.registry.climate(options.climate ?? "temperate").start,
|
|
824
|
+
hours: options.hours,
|
|
825
|
+
sampleMinutes,
|
|
826
|
+
transitions,
|
|
827
|
+
strikes,
|
|
828
|
+
samples
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// src/bench/locale.ts
|
|
833
|
+
var EN = {
|
|
834
|
+
group: "Weather",
|
|
835
|
+
name: "Weather area",
|
|
836
|
+
controls: { state: "Weather", transition: "Transition", time_of_day: "Time of day", auto_schedule: "Automatic schedule", seed: "Seed" },
|
|
837
|
+
hints: {
|
|
838
|
+
transition: "How long the change to the chosen weather takes, in real seconds.",
|
|
839
|
+
time_of_day: "Hour of the day (the world restarts the clock there and keeps the weather).",
|
|
840
|
+
auto_schedule: "On: the climate changes the weather by itself and the clock runs 600 times faster than real time. Off: the weather stays until you change it.",
|
|
841
|
+
seed: "The same seed gives the same schedule and the same lightning."
|
|
842
|
+
},
|
|
843
|
+
options: { instant: "At once", ten_seconds: "10 seconds" },
|
|
844
|
+
layers: { environment: "Write host environment", fog: "Fog in the scene" },
|
|
845
|
+
metrics: {
|
|
846
|
+
state: "Weather",
|
|
847
|
+
hour: "Hour",
|
|
848
|
+
rain: "Rain",
|
|
849
|
+
visibility: "Visibility",
|
|
850
|
+
sight: "Sight scale",
|
|
851
|
+
hearing: "Hearing scale",
|
|
852
|
+
wind: "Wind",
|
|
853
|
+
heat_index: "Heat index",
|
|
854
|
+
wetness: "Ground wetness",
|
|
855
|
+
strikes: "Lightning strikes",
|
|
856
|
+
blend: "Blend",
|
|
857
|
+
temperature: "Temperature"
|
|
858
|
+
},
|
|
859
|
+
scenarios: {
|
|
860
|
+
storm: "A storm sees less and is heard less than a clear day, and strikes lightning",
|
|
861
|
+
blend: "A change blends over its time and settles in the new weather",
|
|
862
|
+
determinism: "The same seed gives the same day, another seed another one",
|
|
863
|
+
dry_ground: "Rain wets the ground and it dries after the rain stops"
|
|
864
|
+
},
|
|
865
|
+
states: {
|
|
866
|
+
clear: "Clear",
|
|
867
|
+
overcast: "Overcast",
|
|
868
|
+
light_rain: "Light rain",
|
|
869
|
+
heavy_rain: "Heavy rain",
|
|
870
|
+
storm: "Storm",
|
|
871
|
+
fog: "Fog",
|
|
872
|
+
windy: "Windy",
|
|
873
|
+
cyclone: "Cyclone",
|
|
874
|
+
typhoon: "Typhoon",
|
|
875
|
+
extreme_heat: "Extreme heat"
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
var PT_BR = {
|
|
879
|
+
group: "Clima",
|
|
880
|
+
name: "\xC1rea de clima",
|
|
881
|
+
controls: { state: "Clima", transition: "Transi\xE7\xE3o", time_of_day: "Hora do dia", auto_schedule: "Sorteio autom\xE1tico", seed: "Semente" },
|
|
882
|
+
hints: {
|
|
883
|
+
transition: "Quanto demora a mudan\xE7a para o clima escolhido, em segundos reais.",
|
|
884
|
+
time_of_day: "Hora do dia (o mundo reinicia o rel\xF3gio nela e mant\xE9m o clima).",
|
|
885
|
+
auto_schedule: "Ligado: o clima do lugar muda sozinho e o rel\xF3gio corre 600 vezes mais r\xE1pido que o real. Desligado: o clima fica at\xE9 voc\xEA trocar.",
|
|
886
|
+
seed: "A mesma semente d\xE1 o mesmo sorteio e os mesmos raios."
|
|
887
|
+
},
|
|
888
|
+
options: { instant: "De uma vez", ten_seconds: "10 segundos" },
|
|
889
|
+
layers: { environment: "Escrever no ambiente do host", fog: "N\xE9voa na cena" },
|
|
890
|
+
metrics: {
|
|
891
|
+
state: "Clima",
|
|
892
|
+
hour: "Hora",
|
|
893
|
+
rain: "Chuva",
|
|
894
|
+
visibility: "Visibilidade",
|
|
895
|
+
sight: "Escala de vis\xE3o",
|
|
896
|
+
hearing: "Escala de audi\xE7\xE3o",
|
|
897
|
+
wind: "Vento",
|
|
898
|
+
heat_index: "\xCDndice de calor",
|
|
899
|
+
wetness: "Ch\xE3o molhado",
|
|
900
|
+
strikes: "Raios",
|
|
901
|
+
blend: "Mistura",
|
|
902
|
+
temperature: "Temperatura"
|
|
903
|
+
},
|
|
904
|
+
scenarios: {
|
|
905
|
+
storm: "Tempestade enxerga e \xE9 ouvida menos que um dia limpo, e solta raios",
|
|
906
|
+
blend: "Uma mudan\xE7a se mistura no tempo dela e assenta no clima novo",
|
|
907
|
+
determinism: "A mesma semente d\xE1 o mesmo dia, outra semente d\xE1 outro",
|
|
908
|
+
dry_ground: "A chuva molha o ch\xE3o e ele seca depois que a chuva para"
|
|
909
|
+
},
|
|
910
|
+
states: {
|
|
911
|
+
clear: "Limpo",
|
|
912
|
+
overcast: "Nublado",
|
|
913
|
+
light_rain: "Chuva fraca",
|
|
914
|
+
heavy_rain: "Chuva forte",
|
|
915
|
+
storm: "Tempestade",
|
|
916
|
+
fog: "Neblina",
|
|
917
|
+
windy: "Ventania",
|
|
918
|
+
cyclone: "Ciclone",
|
|
919
|
+
typhoon: "Tuf\xE3o",
|
|
920
|
+
extreme_heat: "Calor extremo"
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
var weatherBenchTextOf = (locale) => locale === "en" ? EN : PT_BR;
|
|
924
|
+
|
|
925
|
+
// src/bench/module.ts
|
|
926
|
+
var FAST_SCALE = 600;
|
|
927
|
+
var NORMAL_SCALE = 72;
|
|
928
|
+
var BLEND_SECONDS = 10;
|
|
929
|
+
var HOURS_PER_DAY = 24;
|
|
930
|
+
var FOG_NEAR = 1;
|
|
931
|
+
function createWeatherTestModule(options = {}) {
|
|
932
|
+
const locale = options.locale ?? "pt-BR";
|
|
933
|
+
const text = weatherBenchTextOf(locale);
|
|
934
|
+
const { locale: _locale, translate, weather: given, fog: _fog, ...weatherOptions } = options;
|
|
935
|
+
void _locale;
|
|
936
|
+
void _fog;
|
|
937
|
+
const state = {
|
|
938
|
+
seed: options.seed ?? 1,
|
|
939
|
+
hour: options.startHour ?? 8,
|
|
940
|
+
transition: 0,
|
|
941
|
+
auto: options.autoSchedule ?? false,
|
|
942
|
+
environment: true,
|
|
943
|
+
fog: options.fog ?? true
|
|
944
|
+
};
|
|
945
|
+
let host = null;
|
|
946
|
+
let strikes = 0;
|
|
947
|
+
let offStrike;
|
|
948
|
+
let weather = given ?? createWeather({ ...weatherOptions, autoSchedule: state.auto, ...state.auto ? { timeScale: FAST_SCALE } : {} });
|
|
949
|
+
const watch = () => {
|
|
950
|
+
offStrike?.();
|
|
951
|
+
offStrike = weather.on("strike", (_strike) => void (strikes += 1));
|
|
952
|
+
};
|
|
953
|
+
watch();
|
|
954
|
+
const nameOf = (id) => translate?.(`weather.${id}`, locale) ?? text.states[id] ?? id;
|
|
955
|
+
const rebuild = () => {
|
|
956
|
+
const current = weather.state();
|
|
957
|
+
weather = createWeather({ ...weatherOptions, seed: state.seed, start: current, startHour: state.hour, autoSchedule: state.auto, timeScale: state.auto ? FAST_SCALE : NORMAL_SCALE });
|
|
958
|
+
strikes = 0;
|
|
959
|
+
watch();
|
|
960
|
+
};
|
|
961
|
+
const toEnvironment = (sample) => ({
|
|
962
|
+
rain: sample.rain,
|
|
963
|
+
windX: sample.windX,
|
|
964
|
+
windZ: sample.windZ,
|
|
965
|
+
windSpeed: sample.windSpeed,
|
|
966
|
+
gust: sample.gust,
|
|
967
|
+
fogFar: sample.fogFar,
|
|
968
|
+
visibility: sample.effects.visibility,
|
|
969
|
+
nightFactor: sample.nightFactor,
|
|
970
|
+
temperatureC: sample.temperatureC,
|
|
971
|
+
wetness: sample.groundWetness
|
|
972
|
+
});
|
|
973
|
+
const applyFog = (far) => {
|
|
974
|
+
const scene = host?.scene;
|
|
975
|
+
const Fog = host?.THREE?.Fog;
|
|
976
|
+
if (!scene || typeof Fog !== "function") return;
|
|
977
|
+
const fog = scene.fog;
|
|
978
|
+
if (fog && typeof fog.far === "number") fog.far = far;
|
|
979
|
+
else scene.fog = new Fog(11056320, FOG_NEAR, far);
|
|
980
|
+
};
|
|
981
|
+
const publish = () => {
|
|
982
|
+
const sample = weather.sample();
|
|
983
|
+
if (state.environment) host?.environment?.set(toEnvironment(sample));
|
|
984
|
+
if (state.fog) applyFog(sample.fogFar);
|
|
985
|
+
};
|
|
986
|
+
const onChange = (change) => {
|
|
987
|
+
if (change.kind === "layer") {
|
|
988
|
+
if (change.id === "environment" || change.id === "fog") state[change.id] = change.on;
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
if (change.kind === "action") return;
|
|
992
|
+
const { id, value } = change;
|
|
993
|
+
if (id === "state" && typeof value === "string") weather.force(value, state.transition);
|
|
994
|
+
else if (id === "transition" && typeof value === "string") state.transition = value === "instant" ? 0 : BLEND_SECONDS;
|
|
995
|
+
else if (id === "time_of_day" && typeof value === "number") {
|
|
996
|
+
state.hour = value;
|
|
997
|
+
rebuild();
|
|
998
|
+
} else if (id === "auto_schedule" && typeof value === "boolean") {
|
|
999
|
+
state.auto = value;
|
|
1000
|
+
weather.setAutoSchedule(value);
|
|
1001
|
+
weather.setTimeScale(value ? FAST_SCALE : NORMAL_SCALE);
|
|
1002
|
+
} else if (id === "seed" && typeof value === "number") {
|
|
1003
|
+
state.seed = value;
|
|
1004
|
+
rebuild();
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
const controls = () => [
|
|
1008
|
+
{ kind: "choice", id: "state", name: text.controls.state, options: weather.registry.stateIds().map((id) => ({ id, name: nameOf(id) })), default: weather.state() },
|
|
1009
|
+
{
|
|
1010
|
+
kind: "choice",
|
|
1011
|
+
id: "transition",
|
|
1012
|
+
name: text.controls.transition,
|
|
1013
|
+
hint: text.hints.transition,
|
|
1014
|
+
options: [
|
|
1015
|
+
{ id: "instant", name: text.options.instant },
|
|
1016
|
+
{ id: "ten_seconds", name: text.options.ten_seconds }
|
|
1017
|
+
],
|
|
1018
|
+
default: "instant"
|
|
1019
|
+
},
|
|
1020
|
+
{ kind: "number", id: "time_of_day", name: text.controls.time_of_day, hint: text.hints.time_of_day, unit: "h", min: 0, max: HOURS_PER_DAY - 1, step: 1, default: state.hour },
|
|
1021
|
+
{ kind: "boolean", id: "auto_schedule", name: text.controls.auto_schedule, hint: text.hints.auto_schedule, default: state.auto },
|
|
1022
|
+
{ kind: "number", id: "seed", name: text.controls.seed, hint: text.hints.seed, min: 1, max: 9999, step: 1, default: state.seed }
|
|
1023
|
+
];
|
|
1024
|
+
const layers = () => [
|
|
1025
|
+
{ id: "environment", name: text.layers.environment },
|
|
1026
|
+
{ id: "fog", name: text.layers.fog, default: state.fog }
|
|
1027
|
+
];
|
|
1028
|
+
const metrics = () => [
|
|
1029
|
+
{ id: "current_state", name: text.metrics.state, read: () => nameOf(weather.state()) },
|
|
1030
|
+
{ id: "hour", name: text.metrics.hour, unit: "h", digits: 1, read: () => weather.sample().hour },
|
|
1031
|
+
{ id: "blend", name: text.metrics.blend, digits: 2, read: () => weather.sample().blend },
|
|
1032
|
+
{ id: "rain", name: text.metrics.rain, unit: "mm/h", digits: 1, read: () => weather.sample().rain },
|
|
1033
|
+
{ id: "visibility", name: text.metrics.visibility, unit: "m", digits: 0, read: () => weather.sample().effects.visibility },
|
|
1034
|
+
{ id: "sight", name: text.metrics.sight, digits: 2, read: () => weather.sample().effects.sightScale },
|
|
1035
|
+
{ id: "hearing", name: text.metrics.hearing, digits: 2, read: () => weather.sample().effects.hearingScale },
|
|
1036
|
+
{ id: "wind", name: text.metrics.wind, unit: "m/s", digits: 1, read: () => weather.sample().windSpeed },
|
|
1037
|
+
{ id: "temperature", name: text.metrics.temperature, unit: "\xB0C", digits: 1, read: () => weather.sample().temperatureC },
|
|
1038
|
+
{ id: "heat_index", name: text.metrics.heat_index, unit: "\xB0C", digits: 1, read: () => weather.sample().effects.heatIndexC },
|
|
1039
|
+
{ id: "wetness", name: text.metrics.wetness, digits: 2, read: () => weather.sample().groundWetness },
|
|
1040
|
+
{ id: "strikes", name: text.metrics.strikes, digits: 0, read: () => strikes }
|
|
1041
|
+
];
|
|
1042
|
+
const fresh = (extra = {}) => createWeather({ ...weatherOptions, autoSchedule: false, ...extra });
|
|
1043
|
+
const run = (world, seconds) => {
|
|
1044
|
+
for (let elapsed = 0; elapsed < seconds; elapsed += 0.5) world.advance(0.5);
|
|
1045
|
+
};
|
|
1046
|
+
const scenarios = () => [
|
|
1047
|
+
{
|
|
1048
|
+
id: "storm",
|
|
1049
|
+
name: text.scenarios.storm,
|
|
1050
|
+
run: ({ expect }) => {
|
|
1051
|
+
const clear = fresh({ seed: 3, start: "clear" });
|
|
1052
|
+
const storm = fresh({ seed: 3, start: "storm" });
|
|
1053
|
+
let hits = 0;
|
|
1054
|
+
storm.on("strike", () => void (hits += 1));
|
|
1055
|
+
run(storm, 120);
|
|
1056
|
+
const calm = clear.sample().effects;
|
|
1057
|
+
const rough = storm.sample().effects;
|
|
1058
|
+
expect(rough.visibility < calm.visibility, "a storm sees less far", { storm: rough.visibility, clear: calm.visibility });
|
|
1059
|
+
expect(rough.hearingScale < calm.hearingScale, "rain masks sound", { storm: rough.hearingScale, clear: calm.hearingScale });
|
|
1060
|
+
expect(hits > 0, "a storm strikes lightning", { strikes: hits });
|
|
1061
|
+
}
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
id: "blend",
|
|
1065
|
+
name: text.scenarios.blend,
|
|
1066
|
+
run: ({ expect }) => {
|
|
1067
|
+
const world = fresh({ seed: 3, start: "clear" });
|
|
1068
|
+
world.force("heavy_rain", 10);
|
|
1069
|
+
run(world, 5);
|
|
1070
|
+
const middle = world.sample();
|
|
1071
|
+
expect(middle.blend > 0 && middle.blend < 1, "halfway through the change it is still blending", { blend: middle.blend });
|
|
1072
|
+
expect(middle.rain > 0 && middle.rain < 15, "the rain is between the two weathers", { rain: middle.rain });
|
|
1073
|
+
run(world, 6);
|
|
1074
|
+
expect(world.sample().blend === 1 && world.sample().state === "heavy_rain", "after its time it has settled", { blend: world.sample().blend });
|
|
1075
|
+
}
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
id: "determinism",
|
|
1079
|
+
name: text.scenarios.determinism,
|
|
1080
|
+
run: ({ expect }) => {
|
|
1081
|
+
const day = (seed) => JSON.stringify(createWeather({ ...weatherOptions, seed }).timeline(48).samples);
|
|
1082
|
+
expect(day(5) === day(5), "the same seed gives the same two days");
|
|
1083
|
+
expect(day(5) !== day(6), "another seed gives another one");
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
1086
|
+
{
|
|
1087
|
+
id: "dry_ground",
|
|
1088
|
+
name: text.scenarios.dry_ground,
|
|
1089
|
+
run: ({ expect }) => {
|
|
1090
|
+
const world = fresh({ seed: 3, start: "heavy_rain" });
|
|
1091
|
+
run(world, 60);
|
|
1092
|
+
const wet = world.sample().groundWetness;
|
|
1093
|
+
expect(wet > 0.3, "rain wets the ground", { wetness: wet });
|
|
1094
|
+
world.force("clear", 0);
|
|
1095
|
+
run(world, 600);
|
|
1096
|
+
expect(world.sample().groundWetness < wet, "it dries after the rain stops", { wet, after: world.sample().groundWetness });
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
];
|
|
1100
|
+
const module = {
|
|
1101
|
+
id: "weather_area",
|
|
1102
|
+
name: text.name,
|
|
1103
|
+
group: text.group,
|
|
1104
|
+
get weather() {
|
|
1105
|
+
return weather;
|
|
1106
|
+
},
|
|
1107
|
+
items: () => [],
|
|
1108
|
+
controls: () => controls(),
|
|
1109
|
+
layers: () => layers(),
|
|
1110
|
+
metrics: () => metrics(),
|
|
1111
|
+
scenarios: () => scenarios(),
|
|
1112
|
+
attach(lent) {
|
|
1113
|
+
host = lent;
|
|
1114
|
+
const off = lent.bench?.onChange(onChange);
|
|
1115
|
+
publish();
|
|
1116
|
+
const offFrame = lent.onFrame((dt) => {
|
|
1117
|
+
weather.advance(dt);
|
|
1118
|
+
publish();
|
|
1119
|
+
});
|
|
1120
|
+
return () => {
|
|
1121
|
+
off?.();
|
|
1122
|
+
offFrame();
|
|
1123
|
+
host = null;
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
return module;
|
|
1128
|
+
}
|
|
1129
|
+
return __toCommonJS(index_exports);
|
|
1130
|
+
})();
|
|
1131
|
+
//# sourceMappingURL=index.global.js.map
|