@adatechnology/game-weather 0.1.1 → 0.2.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/README.md CHANGED
@@ -1,8 +1,9 @@
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.
4
+ ground wetness, what the weather means for sight, hearing and footsteps, the heat index and the day-night formula, and the bridges that push it to
5
+ the fluid film, the interactables, the vegetation, the effects world, the environment bus and the sound. No three.js and no game content; renderers
6
+ read it. Depends on `game-contracts` and `game-audio` (the rain, wind and thunder recipes); works in the browser and in Node.
6
7
 
7
8
  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
9
  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
@@ -93,3 +94,36 @@ Arte, Galeria do Vale Cinzento); ciclone, tufão e calor intenso não têm fonte
93
94
  - 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
95
  - O solo seca 180 s depois que a chuva para; `effects` entrega `sightScale`, `hearingScale`, `footstepScale`, índice de calor e estresse de calor.
95
96
  - Catálogo por registro: acrescente, troque (`patch`) ou tire estados e climas sem mexer no núcleo.
97
+
98
+ ## Bridges
99
+
100
+ `createWeatherBridges(targets, options)` pushes one weather to whatever you give it. Every target is optional and an interface of its own, so the
101
+ weather imports none of the packages it drives:
102
+
103
+ ```ts
104
+ import { createWeatherBridges } from '@adatechnology/game-weather'
105
+
106
+ const bridges = createWeatherBridges({
107
+ film: room.film, // game-fluids-three FilmBridge: setWeather({ rain, drying }), wash()
108
+ interactables: world, // game-interactables: water.setRain(mm/h), setWind(x, z)
109
+ vegetation, // game-vegetation: setWind({ direction, strength, gust })
110
+ vfx: vfxWorld, // game-vfx: setWind([x, 0, z])
111
+ environment: host.environment, // the EnvironmentBus
112
+ audio: host.audio, // rain and wind loops, thunder
113
+ })
114
+ bridges.bind(weather)
115
+ // every frame, after weather.advance(dt)
116
+ bridges.update(dt)
117
+ ```
118
+
119
+ The rules are numbers you can change (`filmRainFullAt`, `washFrom`, `washBelow`, `washEvery`, `windFull`): light rain (2.5 mm/h) stops the drying and
120
+ does not wash; heavy rain (15 mm/h) and stronger washes the fresh stain (less than half dry) once a second; extreme heat dries twice as fast; the
121
+ vegetation's `strength` is `min(1, speed / 20)`.
122
+
123
+ Sound: `WEATHER_BANK` holds `rain_loop` and `wind_loop` (art scene), `rain_hiss_game` (game) and `thunder`. `createWeatherSound` sets the rain loop
124
+ from 0.035 (light rain) to 0.06 (heavy), the wind loop at 0.05 in the breeze and in proportion to the speed, and plays the thunder at the strike's
125
+ bearing when it arrives (`distance / 343` s after the flash). `options.sound` changes the style (`art_scene` or `game`), the levels and the listener.
126
+
127
+ The picture is `@adatechnology/game-weather-three`. The bench takes it as `visual` (a `WeatherVisualFactory`: `layers(locale)` before the room lends anything, then
128
+ `create({ host, locale, weather })` returning `{ update(dt), setLayer(id, on), dispose() }`); with a visual the bench leaves the scene fog to it:
129
+ `createWeatherTestModule({ visual: createWeatherVisual({ vfx: room.vfx }) })`.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { TestableModule } from '@adatechnology/game-contracts';
1
+ import { Vec3, AudioSink, EnvironmentBus, EnvironmentState, TestableModule, ModuleLayer, ModuleHost } from '@adatechnology/game-contracts';
2
+ import { LoopRecipe, SoundRecipe, SoundBank } from '@adatechnology/game-audio';
2
3
 
3
4
  /** Every number of a weather state; these are the values that blend between two states. */
4
5
  type StateValues = {
@@ -335,6 +336,178 @@ declare function createRng(seed: number): () => number;
335
336
  declare function streamSeed(seed: number, name: string): number;
336
337
  declare function createStream(seed: number, name: string): () => number;
337
338
 
339
+ type WeatherSoundStyle = 'art_scene' | 'game';
340
+ type WeatherSoundOptions = {
341
+ /** Which team's rain: `art_scene` (default, the kit mix) or `game` (a highpass hiss). The wind and the thunder are the same. */
342
+ readonly style?: WeatherSoundStyle;
343
+ /**
344
+ * Level of the rain loop. It is `light` at `lightAt` mm/h (the game's rain, 0.035) and `heavy` from `heavyAt` mm/h (the art scene's, 0.06), a straight
345
+ * line between, and 0 when it does not rain.
346
+ */
347
+ readonly rain?: {
348
+ readonly light?: number;
349
+ readonly heavy?: number;
350
+ readonly lightAt?: number;
351
+ readonly heavyAt?: number;
352
+ };
353
+ /** Level of the wind loop: `base` (the art scene's 0.05) at `referenceSpeed` m/s, in proportion to the speed, at most `max`. */
354
+ readonly wind?: {
355
+ readonly base?: number;
356
+ readonly referenceSpeed?: number;
357
+ readonly max?: number;
358
+ };
359
+ /** Where the listener is, to place the thunder around it (default the origin). */
360
+ readonly listener?: () => Vec3;
361
+ /** Level of the thunder (default 1). */
362
+ readonly thunderVolume?: number;
363
+ /** `false` keeps the sink's bank as it is (the host registered `WEATHER_BANK` itself). */
364
+ readonly register?: boolean;
365
+ };
366
+ declare const DEFAULT_RAIN_SOUND: {
367
+ readonly light: 0.035;
368
+ readonly heavy: 0.06;
369
+ readonly lightAt: 2.5;
370
+ readonly heavyAt: 15;
371
+ };
372
+ /** The art scene opens its wind at 0.05 with the breeze of its day (3.8 m/s, the `clear` state's wind): the level follows the speed from there. */
373
+ declare const DEFAULT_WIND_SOUND: {
374
+ readonly base: 0.05;
375
+ readonly referenceSpeed: 3.8;
376
+ readonly max: 0.12;
377
+ };
378
+ /** The level of the rain loop for `rain` mm/h. */
379
+ declare function rainVolume(rain: number, options?: WeatherSoundOptions['rain']): number;
380
+ /** The level of the wind loop for a wind of `speed` m/s. */
381
+ declare function windVolume(speed: number, options?: WeatherSoundOptions['wind']): number;
382
+ /** Where a strike is, around `origin`: `bearing` is degrees clockwise from north, and north is -z. */
383
+ declare function strikePosition(strike: Pick<Strike, 'distance' | 'bearing'>, origin?: Vec3): Vec3;
384
+ type WeatherSound = {
385
+ /** Sets the level of the loops from the weather of now (opens a loop the first time it is above 0). */
386
+ update(sample: Pick<WeatherSample, 'rain' | 'windSpeed'>): void;
387
+ /** Plays a clap of thunder at the strike (call it when the thunder arrives: the weather delays it by `distance / 343`). */
388
+ thunder(strike: Pick<Strike, 'distance' | 'bearing'>): void;
389
+ dispose(): void;
390
+ };
391
+ /** The sound of the weather on the host's `audio`: the rain loop, the wind loop and the thunder. Without a sink (or one without loops) it does nothing. */
392
+ declare function createWeatherSound(sink: AudioSink | undefined, options?: WeatherSoundOptions): WeatherSound;
393
+
394
+ /** The shape of the film of `game-fluids-three` (`FilmBridge`) this needs: no import, so the weather does not depend on it. */
395
+ type FilmTarget = {
396
+ setWeather(weather: {
397
+ rain: number;
398
+ drying?: number;
399
+ }): void;
400
+ wash?(maxDryness?: number): number;
401
+ };
402
+ /** The interactables world (`game-interactables`): its water takes the rain, the soft volumes the wind. */
403
+ type InteractablesTarget = {
404
+ water: {
405
+ setRain(mmPerHour: number): void;
406
+ };
407
+ setWind(x: number, z: number): void;
408
+ };
409
+ /** The vegetation renderer (`game-vegetation`). `direction` is normalised by the renderer, `strength` and `gust` are 0..1. */
410
+ type VegetationTarget = {
411
+ setWind(wind: {
412
+ direction?: [number, number];
413
+ strength?: number;
414
+ gust?: number;
415
+ }): void;
416
+ };
417
+ /** The effects world (`game-vfx`'s `VfxWorld`): smoke and dust drift with its wind. */
418
+ type VfxTarget = {
419
+ setWind(wind: readonly [number, number, number]): void;
420
+ };
421
+ type WeatherTargets = {
422
+ readonly film?: FilmTarget;
423
+ readonly interactables?: InteractablesTarget;
424
+ readonly vegetation?: VegetationTarget;
425
+ readonly vfx?: VfxTarget;
426
+ /** The host's environment bus (`ModuleHost.environment`). */
427
+ readonly environment?: Pick<EnvironmentBus, 'set'>;
428
+ /** The host's sound (`ModuleHost.audio`): the rain and wind loops, and the thunder. */
429
+ readonly audio?: AudioSink;
430
+ };
431
+ /** The rules the bridges apply; every number can be replaced. */
432
+ type WeatherBridgeRules = {
433
+ /** Rain in mm/h at which the film counts as fully rained on (it stops drying, spreads 1.5 times faster). Default 2.5, the light rain. */
434
+ readonly filmRainFullAt?: number;
435
+ /** Rain in mm/h from which the rain washes the fresh stain (Workshop and visor: heavy rain and stronger). Default 15; light rain does not wash. */
436
+ readonly washFrom?: number;
437
+ /** Stains less dry than this are washed (visor L13494). Default 0.5. */
438
+ readonly washBelow?: number;
439
+ /** Seconds between two washes while it pours. Default 1. */
440
+ readonly washEvery?: number;
441
+ /** Wind speed in m/s that is 1 for the vegetation (`strength = min(1, speed / this)`). Default 20. */
442
+ readonly windFull?: number;
443
+ };
444
+ type WeatherBridgeOptions = WeatherBridgeRules & {
445
+ readonly sound?: WeatherSoundOptions | false;
446
+ };
447
+ declare const DEFAULT_BRIDGE_RULES: {
448
+ readonly filmRainFullAt: 2.5;
449
+ readonly washFrom: 15;
450
+ readonly washBelow: 0.5;
451
+ readonly washEvery: 1;
452
+ readonly windFull: 20;
453
+ };
454
+ /** The weather as the environment bus holds it. */
455
+ declare function environmentOf(sample: WeatherSample): Partial<EnvironmentState>;
456
+ type WeatherBridges = {
457
+ /** Follows this weather (the one before is let go) and pushes its values at once. */
458
+ bind(weather: Weather): void;
459
+ /** Pushes the weather of now to every target; call it each frame after `weather.advance(dt)`. */
460
+ update(dt: number): void;
461
+ dispose(): void;
462
+ };
463
+ /**
464
+ * Pushes one weather to the packages that react to it, each through a small interface of its own, so the weather depends on none of them and every
465
+ * target is optional:
466
+ * - fluids film: `setWeather({ rain: min(1, rain / filmRainFullAt), drying: dryingScale })`, and `wash()` every `washEvery` seconds while the rain is at
467
+ * `washFrom` or stronger (light rain stops the drying and does not wash);
468
+ * - interactables: `water.setRain(mm/h)` and `setWind(x, z)`;
469
+ * - vegetation: `setWind({ direction: [x, z], strength: min(1, speed / windFull), gust: min(1, gust / windFull) })`;
470
+ * - vfx: `setWind([x, 0, z])`;
471
+ * - the environment bus: every value of `environmentOf`;
472
+ * - sound: the rain and wind loops follow the weather, and each thunder that arrives plays at its bearing.
473
+ */
474
+ declare function createWeatherBridges(targets: WeatherTargets, options?: WeatherBridgeOptions): WeatherBridges;
475
+
476
+ /** Rain of the art scene: noise through a highpass at 600 Hz and a lowpass at 6000 Hz (cena L1314), opened at its volume. */
477
+ declare const RAIN_LOOP: LoopRecipe;
478
+ /** Wind of the art scene: noise through a lowpass at 320 Hz whose gain swings 0.035 at 0.07 Hz (cena L1315). */
479
+ declare const WIND_LOOP: LoopRecipe;
480
+ /** Rain of the game: noise through a highpass at 2500 Hz, created when the volume first passes 0.002 (jogo L62116 `chuva`). */
481
+ declare const RAIN_HISS_GAME: LoopRecipe;
482
+ /**
483
+ * Thunder. The thump is the game's boom (a sine from 70 to 28 Hz over 1.2 s); the rumble is a lowpassed noise that slides from 260 to 50 Hz over 3.2 s
484
+ * with a reverb send, the kit's choice (no team has a thunder). It carries much farther than a blast: `k` is 0.00001, so a strike 1 km away is still at
485
+ * 0.13 and one 4 km away (the farthest the weather places) at 0.009.
486
+ */
487
+ declare const THUNDER: SoundRecipe;
488
+ declare const WEATHER_BANK: SoundBank;
489
+ declare const WEATHER_SOUNDS: readonly SoundRecipe[];
490
+ declare const WEATHER_LOOPS: readonly LoopRecipe[];
491
+
492
+ /** What a renderer of the weather (game-weather-three) gives the bench: its layers, a step per frame and the switch of each layer. */
493
+ type WeatherVisual = {
494
+ update(dt: number): void;
495
+ setLayer(id: string, on: boolean): void;
496
+ dispose(): void;
497
+ };
498
+ /** The bench hands a visual the weather it shows (always the live one, even after the bench restarts it) and what the room lends. */
499
+ type WeatherVisualContext = {
500
+ readonly host: ModuleHost;
501
+ readonly locale: string;
502
+ readonly weather: {
503
+ sample(): WeatherSample;
504
+ };
505
+ };
506
+ /** The bench declares its layers before the room lends anything, so the factory tells them first (`fog` is the bench's own layer and reaches the visual through `setLayer` too). */
507
+ type WeatherVisualFactory = {
508
+ layers(locale: string): readonly ModuleLayer[];
509
+ create(context: WeatherVisualContext): WeatherVisual;
510
+ };
338
511
  type WeatherTestModuleOptions = WeatherOptions & {
339
512
  locale?: string;
340
513
  /** Text of a state id in the game's words; without it the bench uses its own names, then the id. */
@@ -343,6 +516,12 @@ type WeatherTestModuleOptions = WeatherOptions & {
343
516
  weather?: Weather;
344
517
  /** 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
518
  fog?: boolean;
519
+ /** The packages the weather drives besides the host's environment bus and sound (fluids film, interactables, vegetation, vfx), from the example. */
520
+ targets?: Omit<WeatherTargets, 'environment' | 'audio'>;
521
+ /** The rain, wind and thunder on the host's `audio`; `false` keeps the area silent. */
522
+ sound?: WeatherSoundOptions | false;
523
+ /** The picture of the weather (rain, fog, sky, lightning, headlights, haze), from `@adatechnology/game-weather-three`. With it the bench leaves the scene fog to the visual. */
524
+ visual?: WeatherVisualFactory;
346
525
  };
347
526
  /** What the scenarios use of the context the test room passes. */
348
527
  interface WeatherScenarioLike {
@@ -373,4 +552,4 @@ type WeatherBenchText = {
373
552
  };
374
553
  declare const weatherBenchTextOf: (locale: string) => WeatherBenchText;
375
554
 
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 };
555
+ export { CLIMATES, type Climate, DAY_REAL_SECONDS, DEFAULT_BRIDGE_RULES, DEFAULT_DRY_OUT_SECONDS, DEFAULT_RAIN_SOUND, DEFAULT_START_HOUR, DEFAULT_TIME_SCALE, DEFAULT_WET_RISE_SECONDS, DEFAULT_WIND_SOUND, DRY_VISIBILITY_METERS, type Daylight, type DeepPartial, type EffectsInput, FLASH_END_SECONDS, type FilmTarget, GAME_DAY_SECONDS, type InteractablesTarget, type LightningConfig, type LightningStream, NIGHT_SIGHT_LOSS, RAIN_HISS_GAME, RAIN_LOOP, type RegistryOptions, SPEED_OF_SOUND, STATE_VALUE_KEYS, type Scheduler, type StateValueKey, type StateValues, type Strike, THUNDER, type TimelineOptions, type VegetationTarget, type VfxTarget, WEATHER_BANK, WEATHER_LOOPS, WEATHER_SOUNDS, WEATHER_STATES, WIND_LOOP, type Weather, type WeatherBenchText, type WeatherBridgeOptions, type WeatherBridgeRules, type WeatherBridges, type WeatherEffects, type WeatherEventMap, type WeatherOptions, type WeatherRegistry, type WeatherSample, type WeatherScenarioLike, type WeatherSound, type WeatherSoundOptions, type WeatherSoundStyle, type WeatherState, type WeatherTargets, type WeatherTestModule, type WeatherTestModuleOptions, type WeatherTimeline, type WeatherVisual, type WeatherVisualContext, type WeatherVisualFactory, type Wetness, type WetnessOptions, blendValues, createLightningStream, createRng, createScheduler, createStream, createTimeline, createWeather, createWeatherBridges, createWeatherRegistry, createWeatherSound, createWeatherTestModule, createWetness, dayFractionAt, daylight, deriveEffects, environmentOf, flashIntensity, heatIndexC, heatStress, rainVolume, streamSeed, strikePosition, thunderDelay, valuesOf, weatherBenchTextOf, windVolume };