@adatechnology/game-weather 0.0.0-stage → 0.1.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.
- 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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anderson Fernandes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,95 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @adatechnology/game-weather
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Pure, seeded weather for games: states from clear to typhoon, climates that move between them in game time, lightning with the thunder delay,
|
|
4
|
+
ground wetness, what the weather means for sight, hearing and footsteps, the heat index and the day-night formula. No three.js, no audio, no
|
|
5
|
+
game content; renderers and sound engines read it. Zero runtime dependencies; works in the browser and in Node.
|
|
6
|
+
|
|
7
|
+
The numbers come from the teams' pages (each state lists its `sources`): the rain, fog and lightning of the VFX Workshop, the dry and wet fog and
|
|
8
|
+
the day of the game, the wind and fog of the Art Scene, the rain rules of the Vale Cinzento gallery. Cyclone, typhoon and extreme heat have no team
|
|
9
|
+
source and are physics presets (and off in the `temperate` climate).
|
|
10
|
+
|
|
11
|
+
- **The normal world comes first.** Every climate starts `clear`; nothing is tied to an outbreak.
|
|
12
|
+
- **States** (`WEATHER_STATES`): `clear`, `overcast`, `light_rain`, `heavy_rain`, `storm`, `fog`, `windy`, `cyclone`, `typhoon`, `extreme_heat`.
|
|
13
|
+
- **Climates** (`CLIMATES`): `temperate` (mostly clear and overcast, no extremes, the default) and `tropical` (extremes rare and enabled).
|
|
14
|
+
A seeded Markov chain in game time: each state lasts 2 to 6 game hours, a change blends over 10 game minutes and the front arrives from the west.
|
|
15
|
+
- **Lightning** has its own seeded stream (the server sends the seed, every client sees the same strikes): the first strike 2 to 4 s after a storm
|
|
16
|
+
begins, then every 3.5 to 6 s, the Workshop's double flash, thunder `distance / 343` seconds later.
|
|
17
|
+
- **Wetness** rises with the rain and is dry 180 s after it stops (faster in extreme heat).
|
|
18
|
+
- **Effects**: `visibility`, `sightScale` (weather times the game's `1 - 0.4 × night`), `hearingScale`, `footstepScale`, `heatIndexC` (NOAA
|
|
19
|
+
Rothfusz), `heatStress` (0..1 on the 27, 32, 41 and 54 °C bands), `lightningReveal`, `dryingScale`, `debrisPush`.
|
|
20
|
+
- **Day and night**: the game's formula (`daylight`), a 20-minute day by default (`timeScale` 72).
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
pnpm add @adatechnology/game-weather
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Browser without a bundler (the global is `GameWeather`):
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<script src="https://unpkg.com/@adatechnology/game-weather/dist/index.global.js"></script>
|
|
32
|
+
<script>
|
|
33
|
+
const weather = GameWeather.createWeather({ seed: 7 })
|
|
34
|
+
</script>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { createWeather } from '@adatechnology/game-weather'
|
|
41
|
+
|
|
42
|
+
const weather = createWeather({ seed: 7, climate: 'tropical' }) // starts clear, 8:00, a 20-minute day
|
|
43
|
+
weather.on('strike', (strike) => flash(strike.bearing))
|
|
44
|
+
weather.on('thunder', (strike) => playThunder(strike.distance))
|
|
45
|
+
weather.on('transition', ({ from, to }) => log(`${from} -> ${to}`))
|
|
46
|
+
|
|
47
|
+
// every frame, with real seconds
|
|
48
|
+
weather.advance(dt)
|
|
49
|
+
const sample = weather.sample()
|
|
50
|
+
sample.rain // mm/h, blended between states
|
|
51
|
+
sample.windX // m/s
|
|
52
|
+
sample.nightFactor // 0 day, 1 night
|
|
53
|
+
sample.groundWetness // 0..1
|
|
54
|
+
sample.effects.hearingScale // multiply a listener's radius by this
|
|
55
|
+
sample.front // { direction: [1, 0], progress } — the curtain moves along +x
|
|
56
|
+
|
|
57
|
+
weather.force('storm', 10) // blend to a storm over 10 real seconds
|
|
58
|
+
weather.timeline(72) // what a fresh world does in 72 game hours (plain JSON, a golden)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Registry: add, patch, remove
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { createWeather, WEATHER_STATES } from '@adatechnology/game-weather'
|
|
65
|
+
|
|
66
|
+
const weather = createWeather({
|
|
67
|
+
states: [{ ...WEATHER_STATES[2]!, id: 'drizzle', sources: ['my game'], rain: 0.5, drops: 300 }],
|
|
68
|
+
patch: { states: { storm: { rain: 90, lightning: { interval: [2, 4] } } }, climates: { temperate: { dwellHours: [1, 3] } } },
|
|
69
|
+
remove: { states: ['fog'] }, // nothing schedules it any more
|
|
70
|
+
})
|
|
71
|
+
weather.registry.patchState('heavy_rain', { visibility: 100 }) // later, with the same verbs
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Determinism
|
|
75
|
+
|
|
76
|
+
The schedule and the lightning draw from separate seeded streams (`streamSeed(seed, 'schedule' | 'lightning')`), so strikes never move the
|
|
77
|
+
schedule and the result does not depend on the size of the steps `advance` is called with. `createTimeline` steps one game minute at a time and is
|
|
78
|
+
checked against goldens on Node 22 and 24.
|
|
79
|
+
|
|
80
|
+
## API
|
|
81
|
+
|
|
82
|
+
Everything is exported from the package root; see `src/*.ts` for the doc comments. Units: metres, seconds, mm/h, degrees Celsius; wind in m/s.
|
|
83
|
+
|
|
84
|
+
## Português
|
|
85
|
+
|
|
86
|
+
Clima puro e determinístico por semente: estados de céu limpo a tufão, climas que passam de um estado a outro em tempo de jogo, relâmpago com o
|
|
87
|
+
atraso do trovão, solo molhado, o que o tempo muda para visão, audição e passos, índice de calor e a fórmula de dia e noite do jogo. Sem three.js,
|
|
88
|
+
sem som, sem conteúdo de jogo: quem desenha e quem toca lê daqui. Cada estado traz `sources` com a origem dos números (Oficina de VFX, jogo, Cena de
|
|
89
|
+
Arte, Galeria do Vale Cinzento); ciclone, tufão e calor intenso não têm fonte de time e são predefinições físicas, desligadas no clima `temperate`.
|
|
90
|
+
|
|
91
|
+
- O mundo normal vem primeiro: todo clima começa em `clear`.
|
|
92
|
+
- O estado dura de 2 a 6 horas de jogo, a mudança se mistura em 10 minutos de jogo e a frente chega de oeste.
|
|
93
|
+
- Relâmpago com fluxo próprio de semente (o servidor manda a semente e todos veem o mesmo clarão); trovão `distância / 343` s depois.
|
|
94
|
+
- O solo seca 180 s depois que a chuva para; `effects` entrega `sightScale`, `hearingScale`, `footstepScale`, índice de calor e estresse de calor.
|
|
95
|
+
- Catálogo por registro: acrescente, troque (`patch`) ou tire estados e climas sem mexer no núcleo.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { TestableModule } from '@adatechnology/game-contracts';
|
|
2
|
+
|
|
3
|
+
/** Every number of a weather state; these are the values that blend between two states. */
|
|
4
|
+
type StateValues = {
|
|
5
|
+
/** Rain in mm/h. */
|
|
6
|
+
readonly rain: number;
|
|
7
|
+
/** Drops the renderer keeps in the air. */
|
|
8
|
+
readonly drops: number;
|
|
9
|
+
/** 0..1: rain sheets (curtains of water) passing. */
|
|
10
|
+
readonly sheets: number;
|
|
11
|
+
/** Wind in m/s along x and z (the rain slants by windX). */
|
|
12
|
+
readonly windX: number;
|
|
13
|
+
readonly windZ: number;
|
|
14
|
+
/** Strongest gust in m/s. */
|
|
15
|
+
readonly gust: number;
|
|
16
|
+
/** Linear fog far distance in metres (render). */
|
|
17
|
+
readonly fogFar: number;
|
|
18
|
+
/** Exponential-squared fog density; 0 means linear fog. */
|
|
19
|
+
readonly fogDensity: number;
|
|
20
|
+
/** Multiplies the sky colour. */
|
|
21
|
+
readonly skyScale: number;
|
|
22
|
+
/** How far one sees for gameplay, in metres (not the render fog). */
|
|
23
|
+
readonly visibility: number;
|
|
24
|
+
/** Relative humidity, 0..1, before the climate offset. */
|
|
25
|
+
readonly humidity: number;
|
|
26
|
+
/** Added to the climate's temperature of the moment. */
|
|
27
|
+
readonly temperatureDeltaC: number;
|
|
28
|
+
/** Ground wetness this state wets up to, 0..1. */
|
|
29
|
+
readonly wetness: number;
|
|
30
|
+
/** Multiplies the radius at which listeners hear (rain masks). */
|
|
31
|
+
readonly hearingScale: number;
|
|
32
|
+
/** Multiplies the noise footsteps make. */
|
|
33
|
+
readonly footstepScale: number;
|
|
34
|
+
/** Multiplies how fast wet ground dries. */
|
|
35
|
+
readonly dryingScale: number;
|
|
36
|
+
/** Acceleration in m/s² that wind puts on loose debris. */
|
|
37
|
+
readonly debrisPush: number;
|
|
38
|
+
/** 0..1: heat haze. */
|
|
39
|
+
readonly haze: number;
|
|
40
|
+
readonly mistPerSecond: number;
|
|
41
|
+
readonly ashPerSecond: number;
|
|
42
|
+
};
|
|
43
|
+
type StateValueKey = keyof StateValues;
|
|
44
|
+
/** Strikes of a state: seconds between them, seconds to the first one, how far they land and how long a flash reveals the scene. */
|
|
45
|
+
type LightningConfig = {
|
|
46
|
+
readonly interval: readonly [min: number, max: number];
|
|
47
|
+
readonly first: readonly [min: number, max: number];
|
|
48
|
+
readonly distance: readonly [min: number, max: number];
|
|
49
|
+
readonly reveal: number;
|
|
50
|
+
};
|
|
51
|
+
type WeatherState = StateValues & {
|
|
52
|
+
readonly id: string;
|
|
53
|
+
/** Where each number comes from: a team source and line, or the design decision that set it. */
|
|
54
|
+
readonly sources: readonly string[];
|
|
55
|
+
readonly lightning?: LightningConfig;
|
|
56
|
+
};
|
|
57
|
+
type Climate = {
|
|
58
|
+
readonly id: string;
|
|
59
|
+
readonly sources: readonly string[];
|
|
60
|
+
readonly start: string;
|
|
61
|
+
/** Game hours a state lasts, drawn uniformly. */
|
|
62
|
+
readonly dwellHours: readonly [min: number, max: number];
|
|
63
|
+
/** Game minutes a change takes to blend. */
|
|
64
|
+
readonly blendMinutes: number;
|
|
65
|
+
/** Where fronts travel to: [1, 0] is toward +x, so they arrive from the west. */
|
|
66
|
+
readonly frontDirection: readonly [x: number, z: number];
|
|
67
|
+
readonly temperatureC: {
|
|
68
|
+
readonly mean: number;
|
|
69
|
+
readonly swing: number;
|
|
70
|
+
};
|
|
71
|
+
readonly humidityOffset: number;
|
|
72
|
+
/** From state to the weights of the next one. A state that is not here, or has nothing reachable, stays. */
|
|
73
|
+
readonly transitions: Readonly<Record<string, Readonly<Record<string, number>>>>;
|
|
74
|
+
};
|
|
75
|
+
type DeepPartial<TValue> = {
|
|
76
|
+
readonly [Key in keyof TValue]?: NonNullable<TValue[Key]> extends readonly unknown[] ? TValue[Key] : NonNullable<TValue[Key]> extends object ? DeepPartial<NonNullable<TValue[Key]>> : TValue[Key];
|
|
77
|
+
};
|
|
78
|
+
type WeatherEffects = {
|
|
79
|
+
/** Gameplay visibility in metres. */
|
|
80
|
+
readonly visibility: number;
|
|
81
|
+
/** Multiplies how far something sees (`perc[0]` of the game): weather times `1 - 0.4 × nightFactor`. */
|
|
82
|
+
readonly sightScale: number;
|
|
83
|
+
/** Multiplies how far something hears (rain masks sound). */
|
|
84
|
+
readonly hearingScale: number;
|
|
85
|
+
/** Multiplies the noise a footstep makes. */
|
|
86
|
+
readonly footstepScale: number;
|
|
87
|
+
readonly heatIndexC: number;
|
|
88
|
+
/** 0..1 on the NOAA bands 27, 32, 41 and 54 °C. */
|
|
89
|
+
readonly heatStress: number;
|
|
90
|
+
/** Seconds a flash lights the scene (0 where there is no lightning). */
|
|
91
|
+
readonly lightningReveal: number;
|
|
92
|
+
readonly dryingScale: number;
|
|
93
|
+
readonly debrisPush: number;
|
|
94
|
+
};
|
|
95
|
+
type Strike = {
|
|
96
|
+
readonly id: number;
|
|
97
|
+
/** Simulation seconds since the weather started. */
|
|
98
|
+
readonly time: number;
|
|
99
|
+
/** Game seconds since the weather started. */
|
|
100
|
+
readonly gameSeconds: number;
|
|
101
|
+
/** Metres from the listener. */
|
|
102
|
+
readonly distance: number;
|
|
103
|
+
/** Degrees clockwise from north. */
|
|
104
|
+
readonly bearing: number;
|
|
105
|
+
/** Seconds from the flash to the thunder: distance / 343. */
|
|
106
|
+
readonly thunderDelay: number;
|
|
107
|
+
};
|
|
108
|
+
type WeatherSample = StateValues & {
|
|
109
|
+
readonly gameSeconds: number;
|
|
110
|
+
/** 0 is midnight, 0.5 is noon. */
|
|
111
|
+
readonly dayFraction: number;
|
|
112
|
+
readonly hour: number;
|
|
113
|
+
readonly nightFactor: number;
|
|
114
|
+
/** 0..1 sun height (the game's `s`). */
|
|
115
|
+
readonly sun: number;
|
|
116
|
+
/** The state being blended to (or settled in), and the one it is leaving while the blend runs. */
|
|
117
|
+
readonly state: string;
|
|
118
|
+
readonly previous: string | undefined;
|
|
119
|
+
/** 0..1: how far the blend has come; 1 is settled. */
|
|
120
|
+
readonly blend: number;
|
|
121
|
+
readonly front: {
|
|
122
|
+
readonly direction: readonly [x: number, z: number];
|
|
123
|
+
readonly progress: number;
|
|
124
|
+
};
|
|
125
|
+
readonly windSpeed: number;
|
|
126
|
+
readonly temperatureC: number;
|
|
127
|
+
/** Relative humidity in percent, with the climate offset. */
|
|
128
|
+
readonly humidityPercent: number;
|
|
129
|
+
/** Ground wetness now, 0..1 (rises with rain, dries 180 s after it stops). */
|
|
130
|
+
readonly groundWetness: number;
|
|
131
|
+
/** 0..1 flash of the latest strike (the Workshop's double flash). */
|
|
132
|
+
readonly flash: number;
|
|
133
|
+
readonly effects: WeatherEffects;
|
|
134
|
+
};
|
|
135
|
+
type WeatherEventMap = {
|
|
136
|
+
readonly transition: {
|
|
137
|
+
readonly from: string;
|
|
138
|
+
readonly to: string;
|
|
139
|
+
readonly gameSeconds: number;
|
|
140
|
+
};
|
|
141
|
+
readonly strike: Strike;
|
|
142
|
+
readonly thunder: Strike;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
declare const WEATHER_STATES: readonly WeatherState[];
|
|
146
|
+
declare const CLIMATES: readonly Climate[];
|
|
147
|
+
type RegistryOptions = {
|
|
148
|
+
/** Added to the shipped states or, when the id exists, replacing it. */
|
|
149
|
+
readonly states?: readonly WeatherState[];
|
|
150
|
+
readonly climates?: readonly Climate[];
|
|
151
|
+
/** Merged into a shipped or added entry by id; a patch for an id that does not exist is ignored. */
|
|
152
|
+
readonly patch?: {
|
|
153
|
+
readonly states?: Readonly<Record<string, DeepPartial<WeatherState>>>;
|
|
154
|
+
readonly climates?: Readonly<Record<string, DeepPartial<Climate>>>;
|
|
155
|
+
};
|
|
156
|
+
readonly remove?: {
|
|
157
|
+
readonly states?: readonly string[];
|
|
158
|
+
readonly climates?: readonly string[];
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
type WeatherRegistry = {
|
|
162
|
+
state(id: string): WeatherState | undefined;
|
|
163
|
+
climate(id: string): Climate | undefined;
|
|
164
|
+
stateIds(): string[];
|
|
165
|
+
climateIds(): string[];
|
|
166
|
+
registerState(state: WeatherState): void;
|
|
167
|
+
registerClimate(climate: Climate): void;
|
|
168
|
+
patchState(id: string, patch: DeepPartial<WeatherState>): void;
|
|
169
|
+
patchClimate(id: string, patch: DeepPartial<Climate>): void;
|
|
170
|
+
removeState(id: string): void;
|
|
171
|
+
removeClimate(id: string): void;
|
|
172
|
+
};
|
|
173
|
+
/** The shipped states and climates with yours added, patched or removed; change it later with the same verbs. */
|
|
174
|
+
declare function createWeatherRegistry(options?: RegistryOptions): WeatherRegistry;
|
|
175
|
+
|
|
176
|
+
declare const DEFAULT_WET_RISE_SECONDS = 120;
|
|
177
|
+
/** The Workshop sheet: the water goes back to the colour of the ground 3 minutes after the rain stops. */
|
|
178
|
+
declare const DEFAULT_DRY_OUT_SECONDS = 180;
|
|
179
|
+
type WetnessOptions = {
|
|
180
|
+
/** Seconds of rain to wet the ground fully. */
|
|
181
|
+
readonly riseSeconds?: number;
|
|
182
|
+
/** Seconds from the end of the rain until the ground is dry. */
|
|
183
|
+
readonly dryOutSeconds?: number;
|
|
184
|
+
};
|
|
185
|
+
type Wetness = {
|
|
186
|
+
value(): number;
|
|
187
|
+
/** Moves `seconds` toward `target` (the wetness the weather asks for); `dryingScale` speeds the dry-out up. Returns the new value. */
|
|
188
|
+
update(seconds: number, target: number, dryingScale?: number): number;
|
|
189
|
+
reset(value?: number): void;
|
|
190
|
+
};
|
|
191
|
+
/** Ground wetness: rises while the weather asks for more, then falls in a straight line that ends `dryOutSeconds` after the start of the fall. */
|
|
192
|
+
declare function createWetness(options?: WetnessOptions): Wetness;
|
|
193
|
+
|
|
194
|
+
declare const DEFAULT_START_HOUR = 8;
|
|
195
|
+
type WeatherOptions = RegistryOptions & {
|
|
196
|
+
/** Seed of the schedule and of the lightning (each has its own stream). Default 1. */
|
|
197
|
+
readonly seed?: number;
|
|
198
|
+
/** Climate id. Default `temperate`. */
|
|
199
|
+
readonly climate?: string;
|
|
200
|
+
/** First state; default the climate's `start`, which is `clear`: the normal world comes first. */
|
|
201
|
+
readonly start?: string;
|
|
202
|
+
/** Game seconds per real second. Default 72 (the 20-minute day). */
|
|
203
|
+
readonly timeScale?: number;
|
|
204
|
+
/** Hour of the day the world starts at. Default 8. */
|
|
205
|
+
readonly startHour?: number;
|
|
206
|
+
/** `false` keeps the state until `force` changes it. Default true. */
|
|
207
|
+
readonly autoSchedule?: boolean;
|
|
208
|
+
readonly wetness?: WetnessOptions;
|
|
209
|
+
/** An existing registry, instead of building one from the registry options. */
|
|
210
|
+
readonly registry?: WeatherRegistry;
|
|
211
|
+
};
|
|
212
|
+
type WeatherTimeline = {
|
|
213
|
+
readonly seed: number;
|
|
214
|
+
readonly climate: string;
|
|
215
|
+
readonly start: string;
|
|
216
|
+
readonly hours: number;
|
|
217
|
+
readonly sampleMinutes: number;
|
|
218
|
+
readonly transitions: ReadonlyArray<{
|
|
219
|
+
readonly gameSeconds: number;
|
|
220
|
+
readonly from: string;
|
|
221
|
+
readonly to: string;
|
|
222
|
+
}>;
|
|
223
|
+
readonly strikes: readonly Strike[];
|
|
224
|
+
readonly samples: ReadonlyArray<Readonly<Record<string, number | string>>>;
|
|
225
|
+
};
|
|
226
|
+
type Weather = {
|
|
227
|
+
readonly registry: WeatherRegistry;
|
|
228
|
+
/** Moves the world forward by `dt` real seconds (game time moves `timeScale` times as fast). Events fire in order as they are crossed. */
|
|
229
|
+
advance(dt: number): void;
|
|
230
|
+
sample(): WeatherSample;
|
|
231
|
+
/** The state being blended to, or settled in. */
|
|
232
|
+
state(): string;
|
|
233
|
+
/** Changes state now, blending over `blendSeconds` real seconds (0 is at once); the new state then lasts a normal dwell. */
|
|
234
|
+
force(id: string, blendSeconds?: number): void;
|
|
235
|
+
setTimeScale(timeScale: number): void;
|
|
236
|
+
setAutoSchedule(isOn: boolean): void;
|
|
237
|
+
/** What a fresh world with these options does over `hours` game hours; the live world is not touched. */
|
|
238
|
+
timeline(hours: number, sampleMinutes?: number): WeatherTimeline;
|
|
239
|
+
on<TEvent extends keyof WeatherEventMap>(event: TEvent, listener: (payload: WeatherEventMap[TEvent]) => void): () => void;
|
|
240
|
+
};
|
|
241
|
+
declare function createWeather(options?: WeatherOptions): Weather;
|
|
242
|
+
type TimelineOptions = WeatherOptions & {
|
|
243
|
+
readonly hours: number;
|
|
244
|
+
/** Game minutes between samples. Default 10. */
|
|
245
|
+
readonly sampleMinutes?: number;
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* What a fresh world does over `hours` game hours, stepping one game minute at a time: the state changes, the strikes (time, distance, bearing) and
|
|
249
|
+
* a sample every `sampleMinutes`. Plain JSON, rounded to 6 decimals, so it can be a golden.
|
|
250
|
+
*/
|
|
251
|
+
declare function createTimeline(options: TimelineOptions): WeatherTimeline;
|
|
252
|
+
|
|
253
|
+
declare const STATE_VALUE_KEYS: readonly StateValueKey[];
|
|
254
|
+
declare function valuesOf(state: WeatherState): StateValues;
|
|
255
|
+
/** Every number between two states: `progress` 0 is `from`, 1 is `to`. */
|
|
256
|
+
declare function blendValues(from: StateValues, to: StateValues, progress: number): StateValues;
|
|
257
|
+
|
|
258
|
+
type Scheduler = {
|
|
259
|
+
/** Game seconds at which the current state ends. */
|
|
260
|
+
nextChangeAt(): number;
|
|
261
|
+
/** Picks the state after `current`, then draws how long it lasts from `at`. Returns the new state's id. */
|
|
262
|
+
next(current: string, at: number): string;
|
|
263
|
+
/** Draws a new duration from `at` (after a forced change). */
|
|
264
|
+
restart(at: number): void;
|
|
265
|
+
};
|
|
266
|
+
/**
|
|
267
|
+
* A seeded Markov chain in game time. Draw order, fixed so the timeline repeats: at start one dwell; at each change the next state first
|
|
268
|
+
* (one draw, over the weights in the climate's order), then its dwell. States `isKnown` rejects are left out of the weights.
|
|
269
|
+
*/
|
|
270
|
+
declare function createScheduler(climate: Climate, seed: number, isKnown: (id: string) => boolean): Scheduler;
|
|
271
|
+
|
|
272
|
+
/** Speed of sound in air, m/s: a physical constant, so it is not configurable. */
|
|
273
|
+
declare const SPEED_OF_SOUND = 343;
|
|
274
|
+
/** Seconds a flash goes on after a strike before the scene is back to normal (Workshop W1068: `k > .4`). */
|
|
275
|
+
declare const FLASH_END_SECONDS = 0.4;
|
|
276
|
+
/** Seconds from the flash to the thunder for a strike `distance` metres away. */
|
|
277
|
+
declare function thunderDelay(distance: number): number;
|
|
278
|
+
/** The Workshop double flash (W1068): full for 0.08 s, 0.2 until 0.16 s, 0.8 until 0.26 s, then dark. `elapsed` is seconds since the strike. */
|
|
279
|
+
declare function flashIntensity(elapsed: number): number;
|
|
280
|
+
type LightningStream = {
|
|
281
|
+
/** Seconds from entering a state with lightning to its first strike. */
|
|
282
|
+
first(config: LightningConfig): number;
|
|
283
|
+
/** Seconds from a strike to the next one. */
|
|
284
|
+
interval(config: LightningConfig): number;
|
|
285
|
+
/** Where a strike lands: metres and degrees clockwise from north. Draws the distance, then the bearing. */
|
|
286
|
+
place(config: LightningConfig): {
|
|
287
|
+
readonly distance: number;
|
|
288
|
+
readonly bearing: number;
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
/** The lightning's own seeded stream, apart from the scheduler's: the server can send the seed and every client sees the same strikes. */
|
|
292
|
+
declare function createLightningStream(seed: number): LightningStream;
|
|
293
|
+
|
|
294
|
+
/** Gameplay visibility of a dry day (the game's dry fog far, jogo.js L69141): `sightScale` is the visibility against it. */
|
|
295
|
+
declare const DRY_VISIBILITY_METERS = 220;
|
|
296
|
+
/** jogo.js L66872: sight is `perc[0] × (1 − 0.4 × nightF)`. */
|
|
297
|
+
declare const NIGHT_SIGHT_LOSS = 0.4;
|
|
298
|
+
type EffectsInput = {
|
|
299
|
+
readonly values: StateValues;
|
|
300
|
+
readonly nightFactor: number;
|
|
301
|
+
readonly temperatureC: number;
|
|
302
|
+
readonly humidityPercent: number;
|
|
303
|
+
readonly lightningReveal: number;
|
|
304
|
+
};
|
|
305
|
+
/** What the weather means for gameplay, as numbers for whoever has eyes, ears or feet in the world (perception, footsteps, heat). */
|
|
306
|
+
declare function deriveEffects(input: EffectsInput): WeatherEffects;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* NOAA heat index in °C: the Steadman estimate averaged with the temperature, and from 80 °F up the Rothfusz regression with the low-humidity and
|
|
310
|
+
* high-humidity adjustments. Built from + - * / and `sqrt` only, so it is the same on every engine.
|
|
311
|
+
*/
|
|
312
|
+
declare function heatIndexC(temperatureC: number, relativeHumidityPercent: number): number;
|
|
313
|
+
/** 0..1 across the NOAA bands (caution 27, extreme caution 32, danger 41, extreme danger 54 °C), linear between them. */
|
|
314
|
+
declare function heatStress(heatIndex: number): number;
|
|
315
|
+
|
|
316
|
+
/** A game day lasts 20 real minutes in the slice (jogo.js L69127: `dayT + dt / 1200`). */
|
|
317
|
+
declare const DAY_REAL_SECONDS = 1200;
|
|
318
|
+
declare const GAME_DAY_SECONDS = 86400;
|
|
319
|
+
/** Game seconds per real second of the 20-minute day. */
|
|
320
|
+
declare const DEFAULT_TIME_SCALE: number;
|
|
321
|
+
type Daylight = {
|
|
322
|
+
/** Sun height 0..1 (`s` of the game). */
|
|
323
|
+
readonly sun: number;
|
|
324
|
+
/** 0 in full daylight, 1 in the darkest night. */
|
|
325
|
+
readonly nightFactor: number;
|
|
326
|
+
};
|
|
327
|
+
/** jogo.js L69128-L69130: `s = clamp(sin(dayT·2π − π/2)·0.5 + 0.5, 0, 1)`, `nightF = 1 − clamp(s·2.2, 0, 1)`. `dayFraction` 0 is midnight. */
|
|
328
|
+
declare function daylight(dayFraction: number): Daylight;
|
|
329
|
+
/** The fraction of the day after `gameSeconds` from `startHour`. */
|
|
330
|
+
declare function dayFractionAt(startHour: number, gameSeconds: number): number;
|
|
331
|
+
|
|
332
|
+
/** Deterministic mulberry32 generator; returns floats in [0, 1). */
|
|
333
|
+
declare function createRng(seed: number): () => number;
|
|
334
|
+
/** A seed for the stream called `name`: the same seed gives each name its own sequence, so lightning never shifts the schedule. */
|
|
335
|
+
declare function streamSeed(seed: number, name: string): number;
|
|
336
|
+
declare function createStream(seed: number, name: string): () => number;
|
|
337
|
+
|
|
338
|
+
type WeatherTestModuleOptions = WeatherOptions & {
|
|
339
|
+
locale?: string;
|
|
340
|
+
/** Text of a state id in the game's words; without it the bench uses its own names, then the id. */
|
|
341
|
+
translate?: (key: string, locale: string) => string | undefined;
|
|
342
|
+
/** The weather to test (default a new one built from the other options). */
|
|
343
|
+
weather?: Weather;
|
|
344
|
+
/** Whether the scene fog follows the weather at first (default true; the layer can be switched either way). A room that is not about weather starts it off. */
|
|
345
|
+
fog?: boolean;
|
|
346
|
+
};
|
|
347
|
+
/** What the scenarios use of the context the test room passes. */
|
|
348
|
+
interface WeatherScenarioLike {
|
|
349
|
+
expect(condition: boolean, label: string, data?: Record<string, unknown>): void;
|
|
350
|
+
}
|
|
351
|
+
type WeatherTestModule = TestableModule<WeatherScenarioLike> & {
|
|
352
|
+
/** The weather in use (replaced when the bench changes the seed or the hour). */
|
|
353
|
+
readonly weather: Weather;
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* The weather area of a test room, written against the generic contracts only: it drives a `createWeather` world, writes the result into the host's
|
|
357
|
+
* environment bus (`host.environment`) every frame and shows the numbers a game reads from it. It also sets the scene fog from the weather's render fog.
|
|
358
|
+
* Pure: no three.js beyond the host's own `Fog`, and the sky, rain, lightning and sound wait for the packages that draw and play them.
|
|
359
|
+
*/
|
|
360
|
+
declare function createWeatherTestModule(options?: WeatherTestModuleOptions): WeatherTestModule;
|
|
361
|
+
|
|
362
|
+
type WeatherBenchText = {
|
|
363
|
+
readonly group: string;
|
|
364
|
+
readonly name: string;
|
|
365
|
+
readonly controls: Readonly<Record<'state' | 'transition' | 'time_of_day' | 'auto_schedule' | 'seed', string>>;
|
|
366
|
+
readonly hints: Readonly<Record<'transition' | 'time_of_day' | 'auto_schedule' | 'seed', string>>;
|
|
367
|
+
readonly options: Readonly<Record<'instant' | 'ten_seconds', string>>;
|
|
368
|
+
readonly layers: Readonly<Record<'environment' | 'fog', string>>;
|
|
369
|
+
readonly metrics: Readonly<Record<'state' | 'hour' | 'rain' | 'visibility' | 'sight' | 'hearing' | 'wind' | 'heat_index' | 'wetness' | 'strikes' | 'blend' | 'temperature', string>>;
|
|
370
|
+
readonly scenarios: Readonly<Record<'storm' | 'blend' | 'determinism' | 'dry_ground', string>>;
|
|
371
|
+
/** Names of the states and climates by id; the bench falls back to the id. */
|
|
372
|
+
readonly states: Readonly<Record<string, string>>;
|
|
373
|
+
};
|
|
374
|
+
declare const weatherBenchTextOf: (locale: string) => WeatherBenchText;
|
|
375
|
+
|
|
376
|
+
export { CLIMATES, type Climate, DAY_REAL_SECONDS, DEFAULT_DRY_OUT_SECONDS, DEFAULT_START_HOUR, DEFAULT_TIME_SCALE, DEFAULT_WET_RISE_SECONDS, DRY_VISIBILITY_METERS, type Daylight, type DeepPartial, type EffectsInput, FLASH_END_SECONDS, GAME_DAY_SECONDS, type LightningConfig, type LightningStream, NIGHT_SIGHT_LOSS, type RegistryOptions, SPEED_OF_SOUND, STATE_VALUE_KEYS, type Scheduler, type StateValueKey, type StateValues, type Strike, type TimelineOptions, WEATHER_STATES, type Weather, type WeatherBenchText, type WeatherEffects, type WeatherEventMap, type WeatherOptions, type WeatherRegistry, type WeatherSample, type WeatherScenarioLike, type WeatherState, type WeatherTestModule, type WeatherTestModuleOptions, type WeatherTimeline, type Wetness, type WetnessOptions, blendValues, createLightningStream, createRng, createScheduler, createStream, createTimeline, createWeather, createWeatherRegistry, createWeatherTestModule, createWetness, dayFractionAt, daylight, deriveEffects, flashIntensity, heatIndexC, heatStress, streamSeed, thunderDelay, valuesOf, weatherBenchTextOf };
|