@adatechnology/game-fluids 0.1.8 → 0.2.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/README.md CHANGED
@@ -8,8 +8,8 @@ Engine-agnostic liquids for games, with no renderer. It knows nothing about any
8
8
  - **Streams**: the jet leaving a lip or a hole. Ballistic, it ends on the first surface it hits (planes with half extents) or on the ground.
9
9
  - **Stains**: grow from a flow front (16 radial samples, never a perfect disc), run downhill, soak into porous surfaces, dry from the edge in and
10
10
  either vanish or leave a permanent mark (coffee, blood, oil).
11
- - **19 built-in liquids** (`LIQUIDS`): water, coffee, tea, milk, soda, juice, beer, wine, spirits, fuel, diesel, motor_oil, cooking_oil, blood,
12
- sap, acid, paint, bleach, honey. Each one has its own colour, viscosity, drying time, gloss and drink effects (`caffeine`, `alcohol`, `poison`,
11
+ - **21 built-in liquids** (`LIQUIDS`): water, coffee, tea, milk, soda, juice, beer, wine, spirits, fuel, diesel, motor_oil, cooking_oil, blood,
12
+ old_blood, bile, sap, acid, paint, bleach, honey. Each one has its own colour, viscosity, drying time, gloss and drink effects (`caffeine`, `alcohol`, `poison`,
13
13
  `disgust`).
14
14
  - **Item uses** (`ITEM_USES`): scripted `sip`, `drink`, `chug`, `pour`, `pour_out`, `open` and `close`, as pure keyframe data plus a player.
15
15
  - Deterministic from a seed (no `Math.random`), so a LAN host can run it and send events. `serialize()` / `load()` round trip the whole world.
@@ -88,15 +88,23 @@ While on it is a `tap` stream (`streams()` lists it with `tap: <id>`): it fills
88
88
  into a cup) and otherwise makes a stain where it lands. Switching emits `{ type: 'tap', tap, on }`. `taps()` returns the state of every tap, and
89
89
  taps are part of `serialize` / `load`.
90
90
 
91
+ ### The team's values as defaults
92
+
93
+ Blood dries in 600 s and leaves a `0x3B1D14` mark, `old_blood` (`0x6C1C16` drying to `0x2A160E`, 360 s) and `bile` (`0x8A9A2A` to `0x3E4220`, 900 s)
94
+ come from the Padrão de Fluidos, fuel evaporates in 60 s without a mark and water takes 180 s (the Oficina de VFX rule table). A game that wants other
95
+ values replaces a liquid by id: `createFluids({ liquids: [{ ...LIQUIDS.find((l) => l.id === 'blood')!, dryTime: 1200, stainColor: 0x4a0506 }] })`.
96
+ `splash(liquid, point, liters, velocity, { tint: { fresh, dry } })` colours one splash (and the stain it makes or joins; the latest tint wins) without
97
+ touching the liquid: the age of an infection is the characters' data, not fluids'.
98
+
91
99
  ### Using it with `@adatechnology/game-destruction`
92
100
 
93
101
  The damage model reports `leak` events for parts that hold a fluid. Map them one to one onto `puncture`; the container then loses liquid by
94
102
  Torricelli and the stream and stains follow:
95
103
 
96
104
  ```ts
97
- import type { DamageEvent } from '@adatechnology/game-destruction'
105
+ import type { DestructionEvent } from '@adatechnology/game-contracts'
98
106
 
99
- destruction.on('leak', (e) => {
107
+ destruction.on('leak', (e: Extract<DestructionEvent, { type: 'leak' }>) => {
100
108
  // e.part is the damaged part, e.point the world hit point of the hole, e.direction where the fluid leaves
101
109
  const id = containerOfPart(e.part)
102
110
  if (!id) return
@@ -133,14 +141,38 @@ While the current key has `action: 'drink'` the player calls `world.drink(rate
133
141
  hand ∘ pose; otherwise the host animates its own viewmodel from `pose()`. `def.liquids` restricts the use to some liquids. Write your own
134
142
  `ItemUseDef` to add `eat`, `wipe` and so on.
135
143
 
144
+ ### Running film (the Fluid Standard)
145
+
146
+ The stain above is an outline. The film is the other model, a literal port of the Fluid Standard's film (F60-114): a grid of 1.25 cm cells
147
+ that holds a thickness in mm and a dryness per cell. A drop lands as an impact stretched by `1 / sin(angle)` with spikes, satellites and
148
+ tails; a 180 Hz thin-film step (at most 6 per frame, limited to the wet box) makes rivulets with a bulb at the tip, runs down the slope and
149
+ over the curb; it dries from the thin edge in. Drips, continuous sources and drag smears are included. It never calls `Math.random`: give it
150
+ the stream, so a LAN client with the same seed draws the same film.
151
+
152
+ ```ts
153
+ import { PADRAO_LAYOUT, createFilm, createFilmLcg, filmProfileOf } from '@adatechnology/game-fluids'
154
+
155
+ const film = createFilm({ layout: PADRAO_LAYOUT, profile: filmProfileOf('blood')!, random: createFilmLcg(1) })
156
+ film.launch([-0.25, 1.3, 0.75], [0.5, 0.2, -6], 2) // position, velocity (m/s), radius in mm
157
+ film.sources.push({ row: 250, column: 120, rate: 240, elapsed: 0, duration: 20, radius: 3 })
158
+ // every frame; dryingSeconds is the simulation clock (a game scales it)
159
+ film.update({ dt, dryingSeconds: dt })
160
+ film.setWeather({ rain: 1 }) // drying stops, spread x1.5 (a no-op until game-weather drives it)
161
+ ```
162
+
163
+ - **Layouts:** `PADRAO_LAYOUT` (256 x 396, wall, sidewalk, curb, road), `WORKSHOP_LAYOUT` (160 x 448), `createFoldedLayout` for your own, and `layoutFromSurfaces({ floor, wall?, curb?, center })`, which builds the fold from your `SurfaceSpec`s (a 160 x 160 patch of 2 x 2 m by default).
164
+ - **Profiles** (`data/film-profiles.json`, `FILM_PROFILES`, `filmProfileOf(id, overrides)`): `blood`, `old_blood`, `bile`, `motor_oil`, `fuel`. `spikes` (bile 0.3: short spikes, thick blobs; 1 = the Standard's), `landsAsBlob` (oil and fuel land as one blob) and `maxAmount` are the Workshop's variants and the defaults.
165
+ - **Simulator:** `FilmSimulator` is the interface of the flow and drying step. The CPU version (`createCpuSimulator`) is the reference the goldens are recorded from; pass `options.simulator` to swap in a GPU one.
166
+
136
167
  ## API
137
168
 
138
169
  | Export | What it is |
139
170
  |---|---|
140
171
  | `createFluids(options?)` | Creates a `FluidWorld`. Options: `liquids`, `locale`, `seed`, `ambientC`, `gravity`, `groundAt(x, z)`, `mergeDistance`, `maxStains`, `onEvent` |
141
- | `LIQUIDS` | The 19 built-in `LiquidDef`s (`id`, `color`, `opacity`, `stainColor`, `viscosity`, `density`, `dryTime`, `leavesMark`, `gloss`, `drink`...) |
172
+ | `LIQUIDS` | The 21 built-in `LiquidDef`s (`id`, `color`, `opacity`, `stainColor`, `viscosity`, `density`, `dryTime`, `leavesMark`, `gloss`, `drink`...) |
142
173
  | `ITEM_USES` | The built-in `ItemUseDef`s |
143
174
  | `createItemUse(world, containerId, def, getHandTransform?)` | Returns an `ItemUsePlayer` (`pose()`, `update(dt)`, `cancel()`, `time`, `done`) |
175
+ | `createFilm(options)`, `createFilmLcg(seed)`, `PADRAO_LAYOUT`, `WORKSHOP_LAYOUT`, `layoutFromSurfaces`, `createFoldedLayout`, `FILM_PROFILES`, `filmProfileOf` | The running film (see above) |
144
176
  | `sampleStream(stream, gravity, t)` | Point of a stream's path at time `t` (0 to `flightTime`) |
145
177
  | `stainDirection(stain, i)`, `STAIN_SAMPLES` | World direction of the front sample `i` of a stain (16 samples) |
146
178
  | `describeEvent(strings, event)`, `resolveStrings(locale)`, `locales` | Localised event text and liquid names |
@@ -156,6 +188,8 @@ integers; density is in kg/m³ and viscosity is relative to water. The surface i
156
188
 
157
189
  ## Português
158
190
 
191
+ > Filme que escorre (Padrão de Fluidos): `createFilm({ layout, profile, random })` simula uma película com espessura em células de 1,25 cm (respingo por ângulo, escorrer, pingar, fontes, arrasto, secar pela borda), com `PADRAO_LAYOUT`, `WORKSHOP_LAYOUT` e `layoutFromSurfaces`. Veja a seção "Running film" acima; `film.setWeather({ rain })` é o gancho do clima.
192
+
159
193
  Líquidos sem renderizador, para qualquer engine: copos, garrafas, galões e tanques com nível calculado pelo formato e pela inclinação,
160
194
  temperatura (esfria e solta vapor), beber, despejar, derramar ao inclinar e vazar por furos (ligue os eventos `leak` do
161
195
  `@adatechnology/game-destruction` em `fluids.puncture`). O líquido que cai vira mancha: cresce a partir de uma frente de fluxo (nunca um disco
package/dist/index.d.ts CHANGED
@@ -40,6 +40,15 @@ interface LiquidDef {
40
40
  };
41
41
  tags?: string[];
42
42
  }
43
+ /** Colours of one splash: the wet film (`fresh`) and what it dries to (`dry`). A game uses it for what the liquid depends on (the age of an infection) while the liquid stays generic. */
44
+ interface StainTint {
45
+ fresh: number;
46
+ dry: number;
47
+ }
48
+ interface SplashOptions {
49
+ /** Overrides the colours the liquid's definition gives the stain this splash makes (or joins; the latest tint wins). */
50
+ tint?: StainTint;
51
+ }
43
52
  interface ContainerState {
44
53
  id: string;
45
54
  liquid: LiquidId | null;
@@ -149,6 +158,8 @@ interface StainState {
149
158
  mark?: boolean;
150
159
  /** Seconds since the stain was created. */
151
160
  age?: number;
161
+ /** The colours of the splash that made it, when it was given a `tint`. */
162
+ tint?: StainTint;
152
163
  }
153
164
  type FluidEvent = {
154
165
  type: 'fill';
@@ -276,8 +287,8 @@ interface FluidWorld {
276
287
  /** Remove a tap (and end its stream). */
277
288
  removeTap(id: string): void;
278
289
  taps(): TapState[];
279
- /** Pour liquid straight onto a point (a broken bottle, a blood splash). */
280
- splash(liquid: LiquidId, point: Vec3, liters: number, velocity?: Vec3): void;
290
+ /** Pour liquid straight onto a point (a broken bottle, a blood splash). `options.tint` colours the stain for this splash only. */
291
+ splash(liquid: LiquidId, point: Vec3, liters: number, velocity?: Vec3, options?: SplashOptions): void;
281
292
  /** Wipe or burn stains in a radius (fire dries water and burns fuel stains). */
282
293
  clearStains(point: Vec3, radius: number): void;
283
294
  update(dt: number): FluidEvent[];
@@ -350,4 +361,339 @@ declare const ballistic: (origin: Vec3, velocity: Vec3, gravity: number, t: numb
350
361
  /** Point of a stream's path `t` seconds after its origin (use with `StreamState.flightTime` and the world's `gravity`). */
351
362
  declare const sampleStream: (stream: Pick<StreamState, "origin" | "velocity">, gravity: number, t: number) => Vec3;
352
363
 
353
- export { type ContainerState, type FluidEvent, type FluidLocale, type FluidOptions, type FluidStrings, type FluidWorld, type HandTransform, ITEM_USES, type ItemUsePlayer, LIQUIDS, type LiquidDef, SAMPLES as STAIN_SAMPLES, type StainState, type StreamCause, type StreamState, type SurfaceSpec, type TapSpec, type TapState, ballistic, createFluids, createItemUse, describeEvent, locales, resolveStrings, sampleStream, stainDirection };
364
+ /** A seeded source of numbers in [0, 1). The film never calls `Math.random`: the host owns the stream, so a replay gets the same film. */
365
+ type FilmRandom = () => number;
366
+ /** Which shader branch draws the liquid (the pages' `uFl`: blood 0, bile 2, oil 3, fuel 4). */
367
+ type FilmLook = 'blood' | 'bile' | 'oil' | 'fuel';
368
+ /** Colours are `0xRRGGBB` integers, thickness is in millimetres (one cell holds that much film). */
369
+ interface FilmProfile {
370
+ id: LiquidId;
371
+ look: FilmLook;
372
+ /** Flow rate of the thin-film step. */
373
+ flow: number;
374
+ /** Thickness that stays behind when the film runs (scaled per cell by the layout's pinning noise). */
375
+ pin: number;
376
+ /** Thickness a dry cell needs to be wetted: it makes the bulb at the tip of a rivulet. */
377
+ front: number;
378
+ referenceThickness: number;
379
+ /** Largest bed height difference the flow sees across one edge. */
380
+ slopeCap: number;
381
+ /** Seconds a thin film takes to dry (0 = never). */
382
+ dryTime: number;
383
+ /** Thickness lost per second at the edge (fuel). */
384
+ evaporation: number;
385
+ opacityRate: number;
386
+ reflectance: number;
387
+ normalScale: number;
388
+ thin: number;
389
+ thick: number;
390
+ dry: number;
391
+ dryThin: number;
392
+ dropColor: number;
393
+ dropOpacity: number;
394
+ /** Volume factor of a landing drop. */
395
+ volume: number;
396
+ /** 1 = the Fluid Standard's long spikes and satellites; below 1 = short spikes and thicker blobs (bile in the Workshop). */
397
+ spikes: number;
398
+ /** A drop of this liquid lands as one rounded blob instead of an impact splash (oil and fuel in the Workshop). */
399
+ landsAsBlob: boolean;
400
+ /** Cap of the thickness a landing drop deposits at its centre. */
401
+ maxAmount: number;
402
+ sources?: Record<string, string>;
403
+ }
404
+ /** The plane of one folded strip of the grid: cell `(row, column)` sits at `origin + column * columnAxis * cell + (row - firstRow) * rowAxis * cell`. */
405
+ interface FilmRegion {
406
+ id: string;
407
+ firstRow: number;
408
+ rowCount: number;
409
+ origin: Vec3;
410
+ columnAxis: Vec3;
411
+ rowAxis: Vec3;
412
+ /** Unit normal pointing out of the surface (towards the air). */
413
+ normal: Vec3;
414
+ /** True for walls and curb faces: the film runs sideways more slowly and keeps less behind. */
415
+ isVertical: boolean;
416
+ }
417
+ /** Where a drop hit a region, in grid terms. `along` is the travel direction on the surface in (column, row) axes; `normalSpeed` is the speed into the surface. */
418
+ interface FilmImpact {
419
+ row: number;
420
+ column: number;
421
+ along: [number, number];
422
+ normalSpeed: number;
423
+ }
424
+ /** The rules that differ between the Fluid Standard and the Workshop (and between host scenes). */
425
+ interface FilmRules {
426
+ /** Drops in flight at once (the ring is reused, oldest overwritten). */
427
+ dropCapacity: number;
428
+ /** A drop falling past the curb edge lands on the curb face. */
429
+ curbCatch: boolean;
430
+ /** A drop below the sidewalk height only counts as on the sidewalk when it is in front of the wall. */
431
+ guardBehindWall: boolean;
432
+ /** Drops landing beyond the end of the road strip are ignored. */
433
+ clipRoadEnd: boolean;
434
+ /** Drops that leave this box are dropped (x and z limits). */
435
+ escapeBox?: {
436
+ minX: number;
437
+ maxX: number;
438
+ maxZ: number;
439
+ };
440
+ /** How the drip draws its bead and jitter: the Standard's formulas or the Workshop's. */
441
+ dripStyle: 'padrao' | 'workshop';
442
+ }
443
+ interface FilmLayout {
444
+ id: string;
445
+ width: number;
446
+ height: number;
447
+ /** Metres per cell. */
448
+ cellSize: number;
449
+ /** Bed height of each cell in mm (what the film runs down), pinning factor and 1/0.95 vertical flag. */
450
+ bed: Float32Array;
451
+ pin: Float32Array;
452
+ vertical: Float32Array;
453
+ regions: readonly FilmRegion[];
454
+ rules: FilmRules;
455
+ /** World position of a (possibly fractional) cell centre-corner. */
456
+ cellToWorld(row: number, column: number): Vec3;
457
+ /** Which region a drop that moved from `previous` to `position` hit (`-1` = none). May snap `position` onto the surface. */
458
+ collide(previous: Vec3, position: Vec3): number;
459
+ impact(region: number, position: Vec3, velocity: Vec3): FilmImpact;
460
+ /** Whether a landing point is on the strip (inside its width and, for the Standard, before the end of the road). */
461
+ contains(position: Vec3): boolean;
462
+ /** Whether a drop that hit nothing has left the area for good. */
463
+ escaped(position: Vec3): boolean;
464
+ }
465
+ /** The grids a film simulates. Flat arrays, `row * width + column`. */
466
+ interface FilmField {
467
+ readonly width: number;
468
+ readonly height: number;
469
+ /** Thickness in mm. */
470
+ readonly thickness: Float32Array;
471
+ /** Dryness 0..1. */
472
+ readonly dryness: Float32Array;
473
+ /** Wet-ground mark 0..1 (gasoline darkens the ground while its film evaporates). */
474
+ readonly dampness: Float32Array;
475
+ }
476
+ interface FilmBounds {
477
+ firstRow: number;
478
+ lastRow: number;
479
+ firstColumn: number;
480
+ lastColumn: number;
481
+ }
482
+ interface FilmAdvanceInput {
483
+ /** Seconds of real time since the last call. */
484
+ dt: number;
485
+ /** Seconds of drying clock that pass in `dt` (simulation time, scaled by the host). */
486
+ dryingSeconds: number;
487
+ }
488
+ /** Steps and dries the film. The CPU version is the reference golden; a GPU version can replace it behind the same interface. */
489
+ interface FilmSimulator {
490
+ /** Wet bounding box used by the last advance (`lastRow < 0` = nothing wet). */
491
+ readonly bounds: FilmBounds;
492
+ /** Recompute the wet bounding box. Call it after anything wrote to the field outside `advance`. */
493
+ refreshBounds(): void;
494
+ /** Runs the flow steps the elapsed time owes (at most `maxSteps`) and the drying. Returns the steps run. */
495
+ advance(input: FilmAdvanceInput): number;
496
+ /** Drops the leftover step time (a new scene). */
497
+ resetClock(): void;
498
+ }
499
+ interface FilmDrip {
500
+ /** World point of the dripping fingertip, moved by the host each frame. */
501
+ position: Vec3;
502
+ /** Seconds to the next drop: `every` is the current wait and `base` the average. */
503
+ every: number;
504
+ base: number;
505
+ elapsed: number;
506
+ /** Drop radius in mm. */
507
+ radius: number;
508
+ }
509
+ interface FilmBead {
510
+ visible: boolean;
511
+ position: Vec3;
512
+ /** Radius in mm of the swelling bead. */
513
+ radius: number;
514
+ /** 0..1 of the way to detaching. */
515
+ swell: number;
516
+ }
517
+ interface FilmSource {
518
+ row: number;
519
+ column: number;
520
+ /** Thickness added per second. */
521
+ rate: number;
522
+ /** Seconds since the start (may start negative to delay it). */
523
+ elapsed: number;
524
+ duration: number;
525
+ /** Radius in cells the deposit scatters over. */
526
+ radius: number;
527
+ }
528
+ interface FilmDrag {
529
+ elapsed: number;
530
+ duration: number;
531
+ /** Half width in cells. */
532
+ width: number;
533
+ amount: number;
534
+ /** One load factor per half-cell across the width (`width * 4 + 1` of them). */
535
+ stripes: number[];
536
+ path(progress: number): {
537
+ row: number;
538
+ column: number;
539
+ };
540
+ /** Throws drops from the moving hand: `height` is the world y they start at, `speed` the travel speed. */
541
+ hand?: {
542
+ height: number;
543
+ speed: number;
544
+ };
545
+ done: boolean;
546
+ }
547
+ interface FilmDropState {
548
+ /** Number of slots (the ring). */
549
+ readonly capacity: number;
550
+ /** Drops launched since the pool was made. */
551
+ readonly launched: number;
552
+ readonly active: Uint8Array;
553
+ /** x, y, z per slot. */
554
+ readonly position: Float64Array;
555
+ readonly velocity: Float64Array;
556
+ /** Radius in mm. */
557
+ readonly radius: Float64Array;
558
+ }
559
+ interface FilmWeather {
560
+ /** 0..1. While it rains drying stops and the film spreads 1.5 times faster. */
561
+ rain: number;
562
+ }
563
+ interface FilmOptions {
564
+ layout: FilmLayout;
565
+ profile: FilmProfile;
566
+ random: FilmRandom;
567
+ /** Flow steps per second of real time (default 180). */
568
+ stepRate?: number;
569
+ /** Most steps in one advance (default 6). */
570
+ maxSteps?: number;
571
+ /** Replace the CPU simulator, for a GPU version. */
572
+ simulator?: (field: FilmField, layout: FilmLayout, profile: () => FilmProfile, options: {
573
+ stepRate: number;
574
+ maxSteps: number;
575
+ weather: () => FilmWeather;
576
+ }) => FilmSimulator;
577
+ }
578
+ interface Film {
579
+ readonly layout: FilmLayout;
580
+ readonly field: FilmField;
581
+ readonly drops: FilmDropState;
582
+ readonly bead: FilmBead;
583
+ readonly simulator: FilmSimulator;
584
+ profile: FilmProfile;
585
+ drip: FilmDrip | undefined;
586
+ readonly sources: FilmSource[];
587
+ readonly drags: FilmDrag[];
588
+ /** Throw a drop of `radius` mm from `position` with `velocity`. */
589
+ launch(position: Vec3, velocity: Vec3, radius: number): void;
590
+ /** The impact splash: a body stretched by `1 / sin(angle)`, spikes, satellites and tails. `row`/`column` and `size` are in cells. */
591
+ splat(row: number, column: number, size: number, directionColumn: number, directionRow: number, sinAngle: number, amount: number): void;
592
+ /** A filled ellipse with a noisy outline. */
593
+ blob(row: number, column: number, along: number, across: number, directionColumn: number, directionRow: number, amount: number, roughness: number): void;
594
+ /** A tapered stroke between two cells. */
595
+ stroke(fromRow: number, fromColumn: number, toRow: number, toColumn: number, startWidth: number, endWidth: number, amount: number): void;
596
+ deposit(row: number, column: number, amount: number): void;
597
+ /** One frame: drops, drip, sources, drags, then the flow steps and the drying. */
598
+ update(input: FilmAdvanceInput): void;
599
+ setWeather(weather: Partial<FilmWeather>): void;
600
+ weather(): FilmWeather;
601
+ /** Empties the film, drops, drip, sources and drags. The ring cursor and the leftover step time stay, as in the pages. */
602
+ clear(): void;
603
+ /** Number of cells above the visible thickness (0.035 mm). */
604
+ wetCells(): number;
605
+ }
606
+
607
+ /**
608
+ * A running film of one liquid over one layout: drops land as impacts, a drip, sources and drag smears add to it, and a 180 Hz
609
+ * thin-film step spreads it limited to the wet box, at most `maxSteps` per frame. It never calls `Math.random`: the host gives the random stream.
610
+ */
611
+ declare function createFilm(options: FilmOptions): Film;
612
+
613
+ type FilmLcg = FilmRandom & {
614
+ seed: number;
615
+ };
616
+ /** The Fluid Standard's generator (LCG 1664525 / 1013904223). `seed` can be read and set, so a scene can be restarted from a number. */
617
+ declare function createFilmLcg(seed: number): FilmLcg;
618
+
619
+ interface FoldedLayoutParams {
620
+ id: string;
621
+ columns: number;
622
+ /** Metres per cell. */
623
+ cellSize: number;
624
+ /** World x of the left edge. */
625
+ originX: number;
626
+ wallRows: number;
627
+ sidewalkRows: number;
628
+ curbRows: number;
629
+ roadRows: number;
630
+ /** World y of the top of the wall strip and of the sidewalk and road planes. */
631
+ wallTopY: number;
632
+ sidewalkY: number;
633
+ roadY: number;
634
+ /** World z of the wall plane (the sidewalk starts here and runs towards +z). */
635
+ wallZ: number;
636
+ /** Metres the curb face drops per row. Default `(sidewalkY - roadY) / curbRows`. */
637
+ curbStep?: number;
638
+ /** x that the road's cross slope is measured from (the road falls `5 mm / m` towards +x from here). */
639
+ roadSlopeAnchorX: number;
640
+ rules: FilmRules;
641
+ }
642
+ /**
643
+ * One strip folded over a wall, the sidewalk in front of it, the curb face and the road: the Fluid Standard's grid (F12-42) and the
644
+ * Workshop's (W562-582) are two settings of it. Bed height (mm), pinning noise and the vertical flag are filled per cell.
645
+ */
646
+ declare function createFoldedLayout(params: FoldedLayoutParams): FilmLayout;
647
+ /** The Fluid Standard's grid: 256 x 396 cells of 1.25 cm over a 1.95 m wall, a 1.6 m sidewalk, a 15 cm curb and a 1.4 m road. */
648
+ declare const PADRAO_LAYOUT: FilmLayout;
649
+ /** The Workshop's grid: 160 x 448 cells (wall 160, sidewalk 128, curb 9, road 151) at the foot of the workshop's wall. */
650
+ declare const WORKSHOP_LAYOUT: FilmLayout;
651
+
652
+ interface SurfaceLayoutOptions {
653
+ /** The surface the film lies on (a floor, a table, a ramp). */
654
+ floor: SurfaceSpec;
655
+ /** A wall that meets the floor: the film folds up it, so a splash on the wall runs down onto the floor. */
656
+ wall?: SurfaceSpec;
657
+ /** The lower surface beyond the floor's far edge: the film folds down a curb face to it. Needs the wall or the floor's tangent to say where the edge is. */
658
+ curb?: SurfaceSpec;
659
+ /** World point the grid is centred on (sideways; and along the floor when there is no wall). */
660
+ center: Vec3;
661
+ /** Cells across (default 160: 2 m of 1.25 cm cells, the grid of the near-player manager). */
662
+ columns?: number;
663
+ /** Cells along all strips together (default 160). */
664
+ rows?: number;
665
+ /** Metres per cell (default 0.0125). */
666
+ cellSize?: number;
667
+ /** Rows of the wall strip (default 48 with a wall, else 0) and cap of the curb face rows (default 12). */
668
+ wallRows?: number;
669
+ curbRows?: number;
670
+ }
671
+ /**
672
+ * Builds the folded grid from the host's surfaces: `wall` (optional) hangs above the fold line, `floor` runs away from the wall, and
673
+ * `curb` (optional) is the lower surface the floor drops to over a curb face. Without a wall the grid is a flat patch of the floor around `center`.
674
+ * Bed heights come from world y, so a tilted floor makes the film run downhill.
675
+ */
676
+ declare function layoutFromSurfaces(options: SurfaceLayoutOptions): FilmLayout;
677
+
678
+ /** Film profiles of the liquids that run as a film (F46-55, plus the Workshop's `spikes` and `landsAsBlob`). Data in `data/film-profiles.json`. */
679
+ declare const FILM_PROFILES: FilmProfile[];
680
+ /** The profile of a liquid with the given overrides, or undefined when the liquid has no film. */
681
+ declare function filmProfileOf(id: LiquidId, overrides?: Partial<FilmProfile>): FilmProfile | undefined;
682
+
683
+ interface CpuSimulatorOptions {
684
+ /** Flow steps per second of real time (the pages use 180). */
685
+ stepRate: number;
686
+ /** Most flow steps in one advance (6 in the Fluid Standard, 9 in the Workshop). */
687
+ maxSteps: number;
688
+ weather: () => FilmWeather;
689
+ }
690
+ /**
691
+ * The thin-film step of the Fluid Standard (F94-114), literal: only film above the pin moves, a dry cell needs the front thickness,
692
+ * walls run sideways at 0.22 and the flow is limited to the wet bounding box. Drying starts from the thin edge.
693
+ */
694
+ declare function createCpuSimulator(field: FilmField, layout: FilmLayout, profileOf: () => FilmProfile, options: CpuSimulatorOptions): FilmSimulator;
695
+
696
+ declare function hashCell(x: number, y: number): number;
697
+ declare function valueNoise(x: number, y: number): number;
698
+
699
+ export { type ContainerState, FILM_PROFILES, type Film, type FilmAdvanceInput, type FilmBead, type FilmBounds, type FilmDrag, type FilmDrip, type FilmDropState, type FilmField, type FilmImpact, type FilmLayout, type FilmLcg, type FilmLook, type FilmOptions, type FilmProfile, type FilmRandom, type FilmRegion, type FilmRules, type FilmSimulator, type FilmSource, type FilmWeather, type FluidEvent, type FluidLocale, type FluidOptions, type FluidStrings, type FluidWorld, type FoldedLayoutParams, type HandTransform, ITEM_USES, type ItemUsePlayer, LIQUIDS, type LiquidDef, PADRAO_LAYOUT, SAMPLES as STAIN_SAMPLES, type SplashOptions, type StainState, type StainTint, type StreamCause, type StreamState, type SurfaceLayoutOptions, type SurfaceSpec, type TapSpec, type TapState, WORKSHOP_LAYOUT, ballistic, createCpuSimulator, createFilm, createFilmLcg, createFluids, createFoldedLayout, createItemUse, describeEvent, filmProfileOf, hashCell, layoutFromSurfaces, locales, resolveStrings, sampleStream, stainDirection, valueNoise };