@codincod/ornament 0.0.0-stage → 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/src/flora.ts ADDED
@@ -0,0 +1,1955 @@
1
+ /**
2
+ * What grows on the bottom: kelp, grass and coral.
3
+ *
4
+ * Rooted life, kept free of the DOM the way the rest of the ornament is. It
5
+ * needs a floor to be rooted to, which the caller supplies as a function rather
6
+ * than a number, because a seabed is not level and a plant hovering a pixel
7
+ * above the sand is a plant nobody believes.
8
+ *
9
+ * A plant asks that function for the ground at its own distance, which is the
10
+ * whole of how a bed acquires depth: the near ones stand on the sand at the
11
+ * front, the far ones are up the slope behind it, and nothing is arranged
12
+ * along a line. See `FloraOptions.floor`.
13
+ *
14
+ * ## Why the plants read as water and the fish do not
15
+ *
16
+ * A fish is going somewhere, so it tells you what it wants and not much about
17
+ * where it is. A plant is not going anywhere, so everything it does is the
18
+ * water doing it. That makes the bed the single most legible statement of
19
+ * current in the whole scene, and it is why these read the same Perlin field
20
+ * the shoal reads: when the water leans one way, the kelp along that stretch
21
+ * leans with it and so do the fish passing through, and the three agreeing is
22
+ * what makes it a sea rather than three animations sharing a rectangle.
23
+ *
24
+ * ## How a strand bends
25
+ *
26
+ * `amp * sin(phase + t * 2.4) * t`, taken from the site's own `Marks.strand/3`
27
+ * and kept identical so a strand in a porthole and a strand on a desktop are
28
+ * the same plant. The `* t` is the whole trick: at the root `t` is zero and the
29
+ * sine cannot move it, so the base is planted and only the tip travels. Drop it
30
+ * and the plant slides along the seabed like weed on a windscreen.
31
+ *
32
+ * Blades grow at whatever angle the strand happens to have where they are, so a
33
+ * blade never crosses the strand it grows from. That is also the site's rule
34
+ * and it is worth keeping for the same reason: a crossing blade is the one
35
+ * thing that makes a drawn plant look like two drawn plants.
36
+ *
37
+ * ## One weed is not a bed
38
+ *
39
+ * There are thousands of seaweeds and this bed knew kelp: a stipe with leaves
40
+ * lying back along it, sown over and over. What the eye reads then is one plant
41
+ * at a range of sizes, and no amount of grading those sizes makes it a bed.
42
+ *
43
+ * So a weed is a row of a table. A frill is the kelp that was here already. A
44
+ * strap is a short thick stipe carrying leaves as long as the whole plant,
45
+ * which is most of what a laminaria is from below. A wrack divides as it
46
+ * climbs, so what it draws is a branch rather than a mast. A sea lettuce is a
47
+ * low ruffle standing off a stub, and it is the only one of them whose outline
48
+ * has any width to it.
49
+ *
50
+ * ## Grass comes in clumps
51
+ *
52
+ * One blade of grass is a hair, and a floor sown with single hairs is a floor
53
+ * that has been swept rather than one anything grows on. Grass grows from a
54
+ * root in a handful of blades of unequal height leaning out of the same spot,
55
+ * so that is what one plant here is: a strand and its mates, splayed a little,
56
+ * each its own height and none of them on the same clock. The clump leans as
57
+ * one because it reads one field at one place, which is what tells you the
58
+ * blades belong to each other rather than happening to stand together.
59
+ *
60
+ * ## And clumps come in meadows
61
+ *
62
+ * Clumps scattered evenly across the box are a lawn somebody seeded by hand.
63
+ * Seagrass spreads from where it already is, so a bed seen from inside it is a
64
+ * few dense patches, a thinning at their edges, and bare sand between them. The
65
+ * beds are placed first and the clumps are placed in them, near the middle more
66
+ * often than at the edge. A few land nowhere near one, because an edge every
67
+ * blade respects is an edge, and a meadow has none.
68
+ *
69
+ * ## Coral does not sway
70
+ *
71
+ * It is a skeleton. A coral bending in the current would be a coral made of
72
+ * rubber, and the stiffness is useful in its own right: with everything else in
73
+ * the scene moving, the one thing holding still is what the movement is
74
+ * measured against.
75
+ *
76
+ * ## A plant recuts itself when it has moved enough to be worth it
77
+ *
78
+ * A bed is the most expensive layer in the ornament, and nearly all of that
79
+ * cost is cutting strands. Almost every plant changes shape on almost every
80
+ * frame, so asking whether one moved saves nothing; asking whether it moved
81
+ * enough to see saves most of the bed, because a plant swaying gently travels a
82
+ * fraction of a pixel between frames and a renderer that redrew it for that
83
+ * spent the frame on a picture nobody could tell from the last one.
84
+ *
85
+ * So a plant holds its last drawing until it has wandered `tolerance` away from
86
+ * it, and `cut` is how a renderer knows. The judgement costs three subtractions
87
+ * and no geometry at all: where the tip of a strand sits is a closed form of
88
+ * the amplitude and the phase, which is what `stir` is. That also takes the
89
+ * work out of the simulation, since cutting the strand was the simulation's own
90
+ * bill; the two were one thing wearing two names.
91
+ *
92
+ * `docs/decisions/0027-let-an-element-decide-when-its-drawing-is-stale.md` is
93
+ * the argument, and `flora.test.ts` holds `stir` to never under-reporting what
94
+ * a plant actually did.
95
+ *
96
+ * ## Two of these are animals
97
+ *
98
+ * An anemone is a column with a crown on it, and everything about it that
99
+ * moves moves on its own. Kelp leans because the water leans; a tentacle feels
100
+ * around whether the water is doing anything or not. That is the difference
101
+ * between a plant and an animal down here, and it is why a crown is walked out
102
+ * of the mouth a step at a time rather than cut from a root: what a tentacle
103
+ * does is curl, and a curl is a heading that keeps turning.
104
+ *
105
+ * An anemone also does the one thing a rooted animal can do about anything: it
106
+ * pulls in. Nothing tells it to. The water is the register of what is in it,
107
+ * and a crown reads that register and decides for itself, which is why `about`
108
+ * is an option here rather than a call somebody makes. What it minds is not
109
+ * what a fish minds: a shark going by at a distance is no business of an
110
+ * anemone's, and something large nearly touching it is all of its business. It
111
+ * pays no attention at all to whether the thing hunts, which every other animal
112
+ * here weighs: what is about to be on top of a crown is about to be on top of
113
+ * it either way, and nothing that eats an anemone was ever going to be outrun.
114
+ *
115
+ * A sea fan is the other end of it. It is a skeleton like coral, so it holds
116
+ * its shape, and it stands across the current rather than along it because
117
+ * that is how it eats. It gives a little, which is what says it is not a stone.
118
+ *
119
+ * The crown earns its place at the back of the picture more than at the front,
120
+ * and it is placed with a lean that way. Everything else rooted down there is a
121
+ * line, and a line a few px long in the murk is a hair the haze takes first. A
122
+ * crown has a body: the tentacles overlap into a mass with an outline, and a
123
+ * mass survives being drawn small and faint where a hair does not. It is the
124
+ * boulder's argument, one layer up.
125
+ */
126
+
127
+ import { abreast, type Presence } from "./biome.ts"
128
+ import { makeNoise2, makeRandom, type Noise2 } from "./perlin.ts"
129
+ import type { Point } from "./seabed.ts"
130
+
131
+ export type PlantKind = "anemone" | "coral" | "fan" | "grass" | "kelp"
132
+
133
+ /**
134
+ * Coral, as a fan of tapering strokes from one base.
135
+ *
136
+ * The site's `Marks.coral/1`, brought across whole. Round caps do the work:
137
+ * strokes of falling width, splayed, read as a branch without a single curve
138
+ * being drawn twice.
139
+ */
140
+ export interface Twig {
141
+ readonly d: string
142
+ readonly width: number
143
+ }
144
+
145
+ /**
146
+ * The same drawing with its strokes gathered by how thick they are.
147
+ *
148
+ * A drawing here is a handful of tapering strokes and a renderer strokes each
149
+ * of them, so a coral of five strokes is five paths, and a bed of corals is
150
+ * more paths than the rest of the scene put together. Strokes of one width are
151
+ * one path as far as a renderer is concerned: several subpaths in one `d`,
152
+ * stroked once, drawn identically. A boulder is four strokes of one width and
153
+ * comes out of this as one.
154
+ *
155
+ * Held on to, because a handful of these tables serve every coral, every reef
156
+ * head and every sprig on a wall, and the gathering is the same each time.
157
+ */
158
+ const gatherings: { drawn: readonly Twig[]; gathered: readonly Twig[] }[] = []
159
+
160
+ export function gathered(twigs: readonly Twig[]): readonly Twig[] {
161
+ for (const held of gatherings) {
162
+ if (held.drawn === twigs) return held.gathered
163
+ }
164
+
165
+ const made: { d: string; width: number }[] = []
166
+ for (const twig of twigs) {
167
+ const same = made.find((one) => one.width === twig.width)
168
+ if (same) same.d += ` ${twig.d}`
169
+ else made.push({ d: twig.d, width: twig.width })
170
+ }
171
+
172
+ gatherings.push({ drawn: twigs, gathered: made })
173
+ return made
174
+ }
175
+
176
+ export const CORAL: readonly Twig[] = [
177
+ { d: "M0 0 C-2 -14 -10 -18 -16 -26", width: 5 },
178
+ { d: "M0 0 C0 -16 2 -24 0 -34", width: 6 },
179
+ { d: "M0 0 C2 -12 10 -16 18 -22", width: 4.5 },
180
+ { d: "M-8 -14 C-12 -20 -16 -22 -22 -24", width: 3 },
181
+ { d: "M8 -12 C12 -18 18 -20 24 -20", width: 3 },
182
+ ]
183
+
184
+ /**
185
+ * The rest of the reef: a bush, a staghorn and a boulder.
186
+ *
187
+ * One coral drawn over and over is a wallpaper repeat, and a seabed made of it
188
+ * is a seabed the eye finishes reading in a second. These are the same idiom as
189
+ * the site's above, tapering strokes from one base, and they differ in the two
190
+ * things that actually change what a growing thing looks like: how wide it gets
191
+ * against how tall, and how thick the strokes are.
192
+ *
193
+ * The boulder is the important one. Everything else rooted down there is a line
194
+ * of some sort, so a bed of them is a bed of hairs however many are sown; the
195
+ * one shape with a body is what the rest gets to stand against.
196
+ */
197
+ const BUSH: readonly Twig[] = [
198
+ { d: "M0 0 C-4 -8 -14 -10 -24 -12", width: 4 },
199
+ { d: "M0 0 C-2 -10 -6 -15 -10 -22", width: 4.5 },
200
+ { d: "M0 0 C1 -9 3 -14 4 -20", width: 5 },
201
+ { d: "M0 0 C4 -8 12 -11 22 -14", width: 4 },
202
+ { d: "M-12 -9 C-16 -14 -18 -16 -20 -22", width: 2.5 },
203
+ { d: "M10 -10 C14 -14 18 -15 22 -20", width: 2.5 },
204
+ ]
205
+
206
+ const STAGHORN: readonly Twig[] = [
207
+ { d: "M0 0 C0 -12 -2 -22 -6 -34", width: 5.5 },
208
+ { d: "M0 0 C3 -10 6 -18 8 -30", width: 4.5 },
209
+ { d: "M-3 -20 C-8 -26 -12 -28 -16 -36", width: 3 },
210
+ { d: "M6 -20 C10 -26 14 -27 18 -32", width: 3 },
211
+ { d: "M0 -6 C-6 -10 -10 -12 -14 -14", width: 2.5 },
212
+ ]
213
+
214
+ const BOULDER: readonly Twig[] = [
215
+ { d: "M0 0 C-9 -4 -13 -9 -12 -15", width: 7 },
216
+ { d: "M0 0 C-4 -6 -5 -12 -4 -18", width: 7 },
217
+ { d: "M0 0 C3 -6 5 -12 5 -17", width: 7 },
218
+ { d: "M0 0 C9 -4 13 -9 11 -15", width: 7 },
219
+ ]
220
+
221
+ /**
222
+ * Which coral a plant might be, and how often.
223
+ *
224
+ * Weighted by repetition, which is the cheapest weighting there is and reads at
225
+ * a glance. Boulders are common because they are what the bed is short of;
226
+ * staghorns are rare because a tall coral in a stand of kelp is one more
227
+ * vertical line in a picture already full of them.
228
+ */
229
+ const REEF: readonly (readonly Twig[])[] = [BOULDER, BOULDER, BUSH, BUSH, CORAL, STAGHORN]
230
+
231
+ /**
232
+ * One blade, as the line it hangs along rather than its endpoints.
233
+ *
234
+ * A blade was two points and a straight line between them, and a plant made of
235
+ * those is a fir tree: every blade the same length, leaving at the same angle,
236
+ * evenly spaced up a trunk. Kelp does not do that. A blade leaves the stipe,
237
+ * bows out into the water and then falls back along it, so what it draws is a
238
+ * long soft curve lying nearly parallel to the plant, and the eye reads a
239
+ * bundle of those as something being held up by water.
240
+ */
241
+ export type Blade = readonly Point[]
242
+
243
+ export interface Plant {
244
+ /**
245
+ * Blades, as they were at the last cut. Empty for coral.
246
+ *
247
+ * Leaves off the stipe for kelp; the rest of the clump for grass. One field
248
+ * rather than two, because a renderer's job is to stroke every line the plant
249
+ * hands it at the plant's own weight, and it has no business knowing which of
250
+ * them is a leaf and which is another blade of grass.
251
+ */
252
+ blades: Blade[]
253
+ /**
254
+ * How many times this plant has recut itself.
255
+ *
256
+ * The only thing a renderer has to watch. While it holds, `points` and
257
+ * `blades` are the arrays they already were, so a renderer keeping its own
258
+ * drawing of this plant keeps a drawing that is still true and does no work.
259
+ * A plant that has never bent bumps it once and then never again, which is
260
+ * every coral in the bed.
261
+ */
262
+ cut: number
263
+ /** 0 is as far off as this water goes, 1 is up against the glass. */
264
+ readonly depth: number
265
+ /** How thick to draw the strand, in px. */
266
+ readonly girth: number
267
+ readonly kind: PlantKind
268
+ /** The strand from root to tip, as it was at the last cut. Empty for coral. */
269
+ points: Point[]
270
+ /** What to scale the coral drawing by. Meaningless for the others. */
271
+ readonly scale: number
272
+ /** Which coral this one is. Empty for anything that sways. */
273
+ readonly twigs: readonly Twig[]
274
+ /** Where it is rooted. */
275
+ readonly x: number
276
+ readonly y: number
277
+ }
278
+
279
+ export interface Flora {
280
+ /**
281
+ * What a plant is made of, for a renderer that would rather bend it itself.
282
+ *
283
+ * The bed hands over points because that is what a renderer needs and the
284
+ * arithmetic behind them is nobody else's business. It is a bad bargain for
285
+ * one kind of caller: a renderer that draws on a graphics card would rather
286
+ * take the shape once and bend it there, and to do that it has to know what
287
+ * the shape is a function of. `Frame` is that, and `Sway` is the two numbers
288
+ * a frame is evaluated at; see `swaying`.
289
+ */
290
+ madeOf(at: number): Frame | null
291
+ readonly plants: readonly Plant[]
292
+ resize(width: number, height: number, floor: (x: number, depth?: number) => number): void
293
+ step(seconds: number): void
294
+ /** Where each plant's sway has got to, in step with `plants`. */
295
+ readonly swinging: readonly Swing[]
296
+ /**
297
+ * A long stretch of time in one go, for a caller winding a scene forward.
298
+ *
299
+ * Exact, not an approximation of one. A plant has no memory: where it is
300
+ * standing at a moment is a sine of the clock and a field read at its own
301
+ * place, so an hour of swaying is an hour added to the clock and one recut.
302
+ * `step` cannot do this because it clamps, which is what protects a scene
303
+ * from a stalled frame, and it is also what makes winding one cost a step per
304
+ * fiftieth of a second of it.
305
+ *
306
+ * It is worth having because the bed is the most expensive layer in the whole
307
+ * ornament by a distance: a wind that recut two hundred plants at every
308
+ * intermediate moment spent three quarters of its time drawing pictures of
309
+ * the past that nobody was ever going to see.
310
+ */
311
+ wind(seconds: number): void
312
+ }
313
+
314
+ export interface FloraOptions {
315
+ /**
316
+ * What else is in the water, asked for once a step.
317
+ *
318
+ * Left out, nothing is, and every crown in the bed stands open. Which is the
319
+ * state a sea with nothing large in it is in anyway.
320
+ */
321
+ about?: () => readonly Presence[]
322
+ anemones?: number
323
+ /**
324
+ * A patch of sand nothing may root in, asked once a plant.
325
+ *
326
+ * Left out, nothing is bare and the bed grows everywhere it can reach. It is
327
+ * here for `eels.ts`, whose whole animal is a stretch of open sand: a colony
328
+ * works its holes over continuously, and a garden eel bed in the wild is a
329
+ * clearing with weed around the edge of it rather than weed with eels in it.
330
+ *
331
+ * A predicate rather than a rectangle, because whatever knows a patch is bare
332
+ * knows why, and a shape handed over here would be that reason copied.
333
+ */
334
+ bare?: (x: number, depth: number) => boolean
335
+ corals?: number
336
+ /**
337
+ * Which kinds the bed should keep a cut drawing of. All of them by default.
338
+ *
339
+ * For a caller that would rather bend a plant itself: hand it `["anemone"]`
340
+ * and the bed still sways, still says where every plant's sway has got to,
341
+ * and stops drawing the four kinds that are a `Frame` and a swing. It is a
342
+ * statement about what the caller is going to read rather than about what the
343
+ * water is doing, which is why it is here and not a tolerance: a plant nobody
344
+ * is going to look at the points of should not be spending anything on them.
345
+ *
346
+ * A crown is the one that has to stay, because a crown is walked rather than
347
+ * cut and there is no frame that stands in for it.
348
+ */
349
+ cutting?: readonly PlantKind[]
350
+ fans?: number
351
+ /**
352
+ * The ground at x, at a given distance; `seabed.floorAt`.
353
+ *
354
+ * Both arguments matter here. A bed where every plant is rooted on the line
355
+ * at the front of the picture is a bed with one plant repeated at a hundred
356
+ * sizes, and no amount of grading those sizes puts distance back into it: the
357
+ * eye reads the row of roots, not the heights. A plant asks for the ground at
358
+ * its own depth, so the roots climb the slope and the bed acquires the one
359
+ * thing a rank of them never has.
360
+ */
361
+ floor: (x: number, depth?: number) => number
362
+ grasses?: number
363
+ height: number
364
+ kelps?: number
365
+ seed: number
366
+ /**
367
+ * How far a plant may wander from its last drawing before it recuts, in px.
368
+ *
369
+ * The bed's whole price, in one number. At nothing, every plant recuts on
370
+ * every step and the bed costs what it always did. At a quarter of a pixel
371
+ * roughly a fifth of them do, and what the eye is given up is a sway that
372
+ * arrives in quarter-pixel steps rather than continuously, on a line drawn at
373
+ * a third of the ink of the copy in front of it.
374
+ *
375
+ * It is a tolerance and not a frame rate: a plant the current is throwing
376
+ * about crosses it every step and keeps every one of its frames, and a plant
377
+ * in the still water at the back of the picture goes minutes without needing
378
+ * one. So a calm bed is nearly free and a stirred one costs what it is worth,
379
+ * which is the right way round and is not something a lower step rate can do.
380
+ */
381
+ tolerance?: number
382
+ width: number
383
+ }
384
+
385
+ /** How many features of the current fit across the box. The shoal's own. */
386
+ const FIELD_CELLS = 3.6
387
+
388
+ /** How many places a plant will try before it gives up on being planted. */
389
+ const BARE_TRIES = 6
390
+
391
+ /** How fast the field slides, so a bed never settles into one shape. */
392
+ const DRIFT = 0.06
393
+
394
+ /** How tall kelp and grass stand, as shares of the box's height. */
395
+ const KELP_LEAST = 0.22
396
+
397
+ const KELP_SPAN = 0.3
398
+
399
+ const GRASS_LEAST = 0.05
400
+
401
+ const GRASS_SPAN = 0.085
402
+
403
+ /**
404
+ * How many blades stand in one clump of grass, and how far they splay.
405
+ *
406
+ * The splay is an angle off upright, in radians, and it is small on purpose. A
407
+ * clump that fans wide is a palm; grass leaves the ground very nearly vertical
408
+ * and only opens out towards the tips, which the sway already does for it.
409
+ *
410
+ * The shortest blade in a clump is `GRASS_RUNT` of the tallest. Even heights
411
+ * are the thing that gives a drawn plant away fastest, faster than an even
412
+ * spacing does.
413
+ */
414
+ /**
415
+ * How much grass a full clump wants, in px, and the fewest blades it is still a
416
+ * clump with.
417
+ *
418
+ * The crown's argument again. A clump reads as one plant opening out, and the
419
+ * blade that says so is the one leaning furthest; the ones between it and the
420
+ * middle are what make it thick. At the back of the bed there is no room
421
+ * between them to be thick in.
422
+ */
423
+ const TUFT_CROWDED = 46
424
+
425
+ const TUFT_FEWEST = 2
426
+
427
+ const TUFT_LEAST = 3
428
+
429
+ const TUFT_MOST = 7
430
+
431
+ const TUFT_SPLAY = 0.34
432
+
433
+ const TUFT_ROOT = 0.16
434
+
435
+ const GRASS_RUNT = 0.5
436
+
437
+ /** How thick their strands are drawn, in px. */
438
+ const KELP_GIRTH = 5.2
439
+
440
+ const GRASS_GIRTH = 2
441
+
442
+ /** How finely a strand is cut. Enough that the bend has no corners in it. */
443
+ const KELP_STEPS = 12
444
+
445
+ const GRASS_STEPS = 6
446
+
447
+ /**
448
+ * How far a tip travels, as a share of the plant's own height.
449
+ *
450
+ * Kelp leans a long way and grass barely does, which is the difference between
451
+ * something with a metre of leverage and something with a hand's width of it.
452
+ * Both are measured against their own height rather than in pixels, so a tall
453
+ * plant and a short one in the same current agree about which way the water is
454
+ * going and disagree about how much it matters.
455
+ */
456
+ const KELP_LEAN = 0.34
457
+
458
+ const GRASS_LEAN = 0.14
459
+
460
+ /** How fast a plant works through its own sway, slowest to fastest. */
461
+ const SWAY_SLOWEST = 0.22
462
+
463
+ const SWAY_FASTEST = 0.44
464
+
465
+ /**
466
+ * How much of the lean is the shared current, and how much is the plant's own.
467
+ *
468
+ * Mostly shared, because a bed where every plant swings on its own clock is a
469
+ * bed in no current at all: what the eye reads then is a row of metronomes. The
470
+ * share that is its own is what stops them moving as one object.
471
+ */
472
+ const CURRENT_SHARE = 0.68
473
+
474
+ /**
475
+ * Which weed a plant is, as the handful of numbers that tell one from another.
476
+ *
477
+ * What separates two seaweeds in silhouette is small: how long a leaf is
478
+ * against the plant carrying it, how much of that length is spent trailing
479
+ * back down the strand rather than standing off it, where up the strand the
480
+ * leaves start, and whether the strand divides on the way up. The rest of the
481
+ * plant is the same plant.
482
+ *
483
+ * `span`, `trail` and `flare` are the three that carry it, and they are where
484
+ * kelp used to keep its only shape. A leaf leaving along the normal stands out
485
+ * like a thorn, and a plant covered in short thorns reads as something dead in
486
+ * winter. So the two directions run on different clocks along the leaf: the
487
+ * sideways part is all at the root and gone by the tip, the trailing part
488
+ * builds the whole way, and between them the leaf bows out and comes back.
489
+ * `flare` only has to be enough to keep a leaf off the strand it grows from,
490
+ * and every bit past that is a thorn again, which is why the one weed here
491
+ * that flares hard has almost no trail to lie along.
492
+ */
493
+ interface Weed {
494
+ /** How many leaves come off the strand. */
495
+ readonly blades: number
496
+ /** How far a leaf bows off the strand before it falls back along it. */
497
+ readonly flare: number
498
+ /** Where up the strand it divides, if it divides at all. */
499
+ readonly forks: readonly number[]
500
+ /** How thick its strand is drawn, against kelp's own. */
501
+ readonly girth: number
502
+ /** Where up the strand the lowest leaf sits. */
503
+ readonly seat: number
504
+ /** How long a leaf is against the plant's height. */
505
+ readonly span: number
506
+ /** How tall it stands, against kelp's own height. */
507
+ readonly stature: number
508
+ /** How much shorter the topmost leaf is than the lowest. */
509
+ readonly taper: number
510
+ /** How much of a leaf's length is spent going back down the strand. */
511
+ readonly trail: number
512
+ }
513
+
514
+ /** The kelp that was here before there was a table: a mast of long soft leaves,
515
+ * every one of them lying back along it. */
516
+ const FRILL: Weed = {
517
+ blades: 7,
518
+ flare: 0.3,
519
+ forks: [],
520
+ girth: 1,
521
+ seat: 0.14,
522
+ span: 0.44,
523
+ stature: 1,
524
+ taper: 0.45,
525
+ trail: 0.95,
526
+ }
527
+
528
+ /** A strap: a short thick stipe under leaves as long as the plant, which is
529
+ * most of what a laminaria is from below. */
530
+ const STRAP: Weed = {
531
+ blades: 4,
532
+ flare: 0.42,
533
+ forks: [],
534
+ girth: 1.35,
535
+ seat: 0.55,
536
+ span: 0.95,
537
+ stature: 0.74,
538
+ taper: 0.08,
539
+ trail: 0.8,
540
+ }
541
+
542
+ /** A wrack: it divides as it climbs, so its outline is a branch, and what it
543
+ * carries along the branches are stubs rather than leaves. */
544
+ const WRACK: Weed = {
545
+ blades: 24,
546
+ flare: 0.28,
547
+ forks: [0.3, 0.48, 0.66],
548
+ girth: 0.8,
549
+ seat: 0.2,
550
+ span: 0.34,
551
+ stature: 0.78,
552
+ taper: 0.3,
553
+ trail: 0.85,
554
+ }
555
+
556
+ /** Sea lettuce: a low ruffle standing off a stub, and the only weed here whose
557
+ * outline has any width to it. */
558
+ const LETTUCE: Weed = {
559
+ blades: 15,
560
+ flare: 0.95,
561
+ forks: [],
562
+ girth: 0.62,
563
+ seat: 0.06,
564
+ span: 0.33,
565
+ stature: 0.44,
566
+ taper: 0.05,
567
+ trail: 0.2,
568
+ }
569
+
570
+ /**
571
+ * Which weed a plant might be, and how often.
572
+ *
573
+ * Weighted by repetition, the way the reef above is. The frill comes up most
574
+ * because it is the tall thing in the bed, and a bed with nothing tall in it is
575
+ * a lawn.
576
+ */
577
+ const WEEDS: readonly Weed[] = [FRILL, FRILL, LETTUCE, STRAP, WRACK]
578
+
579
+ /**
580
+ * How much weed a full set of leaves wants, in px, and the fewest it is a weed
581
+ * with.
582
+ *
583
+ * The crown's argument on the tallest thing in the bed, which is why the number
584
+ * is the largest of them. A wrack carries two dozen stubs along its branches
585
+ * and stands anywhere from a knuckle to half the box; at the short end those
586
+ * stubs are closer together than the strand they are on is thick.
587
+ */
588
+ const LEAF_CROWDED = 210
589
+
590
+ const LEAF_FEWEST = 3
591
+
592
+ /** How finely a leaf is cut. Enough that the bow in it has no corner. */
593
+ const BLADE_STEPS = 5
594
+
595
+ /**
596
+ * How much of a line one segment is worth, in px, and the fewest any line is
597
+ * ever cut into.
598
+ *
599
+ * Every count in this file says how finely something is cut and none of them
600
+ * said how big the something was drawn. A plant in the murk is a few px tall
601
+ * and was carrying the same twelve-point strand and the same five-point leaves
602
+ * as one at the glass, which is a point every fraction of a pixel: work done,
603
+ * published and tessellated for a bend nothing can resolve.
604
+ *
605
+ * Depth never changes for anything rooted, so the count is dealt when the thing
606
+ * is sown and then left alone. That is the whole reason to do it this way: a
607
+ * count that changed while somebody was watching would pop, and one dealt at
608
+ * sowing cannot.
609
+ *
610
+ * The number is the chord: a segment this long on a bend as slack as a plant's
611
+ * stands about a quarter of a px off the curve it is standing in for, which is
612
+ * the same quarter px the bed already accepts before it redraws at all. The
613
+ * table's own count stays the ceiling, so nothing is ever cut finer than it was.
614
+ */
615
+ const STEP_SPAN = 8
616
+
617
+ const FEWEST_STEPS = 2
618
+
619
+ /** How finely a line that long is worth cutting, against the most it may have. */
620
+ function cuts(most: number, drawn: number): number {
621
+ return Math.max(FEWEST_STEPS, Math.min(most, Math.round(drawn / STEP_SPAN)))
622
+ }
623
+
624
+ /**
625
+ * How many of something a drawing that big is worth.
626
+ *
627
+ * `roomy` is how much room the full count wants, in px, and `fewest` is where
628
+ * the thing stops being itself. A crown had this argument first and made it
629
+ * alone; it is the same argument for a fan's ribs and a clump's blades, so it
630
+ * is written once and the three of them pass their own numbers to it.
631
+ */
632
+ function crowded(most: number, fewest: number, drawn: number, roomy: number): number {
633
+ const room = Math.min(1, Math.max(0, drawn / roomy))
634
+ return Math.max(Math.min(most, fewest), Math.round(most * room))
635
+ }
636
+
637
+ /**
638
+ * What a fork takes of the strand left above it, and how far off it leans.
639
+ *
640
+ * Well short of all of it, and well short of a right angle. A fork carrying the
641
+ * whole of the remaining height is a plant with two tips at the same place,
642
+ * which reads as one strand drawn twice; a fork leaving sideways is a
643
+ * crossroads.
644
+ */
645
+ const FORK_REACH = 0.58
646
+
647
+ const FORK_SPLAY = 0.5
648
+
649
+ /** The ends of a coral's scale. */
650
+ const CORAL_SMALLEST = 0.9
651
+
652
+ const CORAL_LARGEST = 2.4
653
+
654
+ /**
655
+ * How tall an anemone stands, as a share of the box, and how thick its column
656
+ * is against that.
657
+ *
658
+ * Short and stout. What an anemone has instead of height is a crown, and a tall
659
+ * one is a plant with a bad haircut. The column is measured as a share of its
660
+ * own height rather than in px because what says column is that it is stout,
661
+ * and something stout that got small stayed stout.
662
+ */
663
+ const ANEMONE_LEAST = 0.03
664
+
665
+ const ANEMONE_SPAN = 0.028
666
+
667
+ const ANEMONE_GIRTH = 0.15
668
+
669
+ /** How far a column gives to the water, and how finely it is cut. It is a
670
+ * foot: it holds on, and what moves up there is the crown. */
671
+ const ANEMONE_LEAN = 0.08
672
+
673
+ const ANEMONE_STEPS = 3
674
+
675
+ /**
676
+ * How many tentacles a crown carries, and how far the outermost opens from
677
+ * upright, in radians.
678
+ *
679
+ * Enough of them that they overlap, which is the whole of why an anemone is
680
+ * worth having at the back of the picture: the mass has an outline and a hair
681
+ * does not. Opened to about a right angle at the edge, so the crown is a bowl
682
+ * rather than a brush.
683
+ */
684
+ const CROWN_LEAST = 12
685
+
686
+ const CROWN_MOST = 20
687
+
688
+ const CROWN_OPEN = 0.92
689
+
690
+ /**
691
+ * How much further a tentacle turns along its own length, how long it is
692
+ * against the column, and how far it feels around as it goes.
693
+ *
694
+ * The wave is scaled by how far along the tentacle it is, for the reason a
695
+ * strand's bend is: what a tentacle is attached to holds still, and only the
696
+ * tip is free. The curl is the shape and the wave is the life, and the wave is
697
+ * the smaller of the two by a distance.
698
+ */
699
+ const CROWN_CURL = 0.36
700
+
701
+ const CROWN_REACH = 0.95
702
+
703
+ const CROWN_WAVE = 0.22
704
+
705
+ const CROWN_WAVES = 2.2
706
+
707
+ const CROWN_STEPS = 5
708
+
709
+ /**
710
+ * How much column a crown wants, in px, before it is dealt every tentacle it
711
+ * could have, and the fewest it is ever dealt.
712
+ *
713
+ * A crown reads because its tentacles overlap into a mass. The count that does
714
+ * that at the size the bed grows them is a smudge at the size something else
715
+ * might: a head on a reef is a fraction of an anemone on open sand, and the
716
+ * same handful of lines crossing in that space is a thumbprint. So the count
717
+ * comes down with the drawing, and the floor is where it stops being a crown
718
+ * and starts being a tuft.
719
+ */
720
+ const CROWN_CROWDED = 30
721
+
722
+ const CROWN_FEWEST = 6
723
+
724
+ /**
725
+ * How much bigger than its own crown a thing has to be before an anemone minds
726
+ * it, and how near it has to come, as a share of its own size.
727
+ *
728
+ * Far nearer than a fish minds anything. A clownfish watches the water because
729
+ * it can leave; a crown has nowhere to be but where it is, so the only thing
730
+ * worth spending a retraction on is something about to be on top of it. Minded
731
+ * from further off, a bed of them would shut every time anything crossed the
732
+ * picture, and a shut anemone is a stalk.
733
+ */
734
+ const CROWN_MINDS = 2.4
735
+
736
+ const CROWN_NOTICE = 0.9
737
+
738
+ /**
739
+ * How much of itself a frightened crown pulls in, how far the column squats
740
+ * under it, and how long it takes to open again.
741
+ *
742
+ * Slow. A pull is fast because it has to be and the opening is not, which is
743
+ * most of what makes it read as an animal rather than a switch. Not the whole
744
+ * of the tentacle either: an anemone closed to nothing is a stalk, and the
745
+ * shape that says frightened is a crown half its own width with its column
746
+ * hunched under it.
747
+ */
748
+ const CROWN_PULL = 0.62
749
+
750
+ const COLUMN_SQUAT = 0.22
751
+
752
+ const PULL_FADE = 6
753
+
754
+ /** How tall a sea fan stands, as a share of the box, and how thick its stem is
755
+ * against that. Broad rather than tall: it is a net held up in the current. */
756
+ const FAN_LEAST = 0.055
757
+
758
+ const FAN_SPAN = 0.05
759
+
760
+ const FAN_GIRTH = 0.07
761
+
762
+ /**
763
+ * How much of a fan's height is the stem the ribs leave from.
764
+ *
765
+ * A third, and it matters. Cut at full height it is a fan with a flagpole
766
+ * through it: a bare mast standing well above the last rib, which no gorgonian
767
+ * has and which the eye reads before it reads the fan.
768
+ */
769
+ const FAN_STEM = 0.34
770
+
771
+ /**
772
+ * How far a fan gives to the current, how finely it is cut, and what share of
773
+ * the bend its ribs take.
774
+ *
775
+ * Barely, and the barely is the point. A gorgonian is a skeleton, so it holds
776
+ * its shape the way coral does; give it kelp's lean and it is made of rubber,
777
+ * and give it none at all and it is a stone with a pattern on it.
778
+ */
779
+ const FAN_LEAN = 0.06
780
+
781
+ const FAN_STEPS = 4
782
+
783
+ const RIB_GIVE = 0.5
784
+
785
+ /** How many ribs leave the stem, how far the lowest opens from upright, and
786
+ * where up the stem the first of them leaves. */
787
+ const RIB_LEAST = 6
788
+
789
+ const RIB_MOST = 10
790
+
791
+ /**
792
+ * How much fan a full set of ribs wants, in px, and the fewest it is a fan
793
+ * with.
794
+ *
795
+ * The crown's argument, on the plant it applies to hardest. A gorgonian is
796
+ * mostly rib, and the bed grows them at about two dozen px tall: eight ribs
797
+ * with a twig off each is sixteen lines crossing a space a thumbnail would
798
+ * cover, and what comes out is a blot rather than a lattice. Three ribs is
799
+ * where the splay stops reading.
800
+ */
801
+ const RIB_CROWDED = 44
802
+
803
+ const RIB_FEWEST = 3
804
+
805
+ /** How much rib a twig off the middle of it wants, in px. Under this the twig
806
+ * lands inside the line it left, and is a point of ink nobody can resolve. */
807
+ const RIB_TWIGGED = 14
808
+
809
+ const RIB_OPEN = 1.25
810
+
811
+ const RIB_SEAT = 0.12
812
+
813
+ /** How long a rib is against the stem, what a twig off one takes of that, and
814
+ * how much further out the twig leans. */
815
+ const RIB_REACH = 0.95
816
+
817
+ const RIB_TWIG = 0.38
818
+
819
+ const RIB_SPLAY = 1.35
820
+
821
+ /**
822
+ * How much width one bed of grass gets, how far a clump strays inside it, how
823
+ * far it strays in distance, and how often one lands nowhere near a bed.
824
+ *
825
+ * The width is in px so that a wide screen gets more beds rather than wider
826
+ * ones, which is the difference between a coast with meadows along it and one
827
+ * meadow stretched to fit. The stray is what keeps a bed from having an edge.
828
+ */
829
+ const MEADOW_EVERY = 620
830
+
831
+ const MEADOW_SPREAD = 0.13
832
+
833
+ const MEADOW_DEEP = 0.16
834
+
835
+ const MEADOW_STRAY = 0.14
836
+
837
+ /**
838
+ * How far back rooted things stand, from the murk to the front of the picture.
839
+ *
840
+ * The near end used to stop short of the front on the argument that the bed is
841
+ * behind the water rather than in front of it. That was true of a bed drawn as
842
+ * one line, where the nearest plant and the furthest stood on the same edge and
843
+ * the only thing distance did was thin them. A plant now stands on the ground
844
+ * at its own distance, so the near end has to reach the sand at the front: stop
845
+ * short of it and the whole foreground is bare ground with the whole bed
846
+ * hanging above it.
847
+ *
848
+ * The far end reaches most of the way back for the same reason read the other
849
+ * way round. Stop it short and the hills at the back are bare rock with a lawn
850
+ * in front of them, and the two read as separate pictures: what ties a distance
851
+ * to a foreground is one kind of thing appearing at every station between them.
852
+ * What a plant that far back is drawn at is `PLANT_SHRINK`, which is the answer
853
+ * to the version of this that was tried first and did not work: reaching back
854
+ * with the size scaled by the distance outright put a hair on the far ground
855
+ * and the haze had it before anybody did.
856
+ */
857
+ const DEPTH_FAR = 0.12
858
+
859
+ const DEPTH_NEAR = 1
860
+
861
+ /**
862
+ * How much of a plant's drawn size is how far off it is.
863
+ *
864
+ * Not the whole of it, for the reason `seabed.ts` gives about a stone: scaled
865
+ * by distance outright, a plant on the far hills is a hair a couple of px wide,
866
+ * the haze has most of that, and what the eye reads up there is bare ground
867
+ * with a lawn in front of it. A share leaves distance most of its work and
868
+ * leaves the crest something a reader can name as a plant.
869
+ */
870
+ const PLANT_SHRINK = 0.78
871
+
872
+ /** How much of its size a plant that far off keeps. */
873
+ const shrunk = (depth: number): number => 1 - PLANT_SHRINK + PLANT_SHRINK * depth
874
+
875
+ /**
876
+ * How hard a crown leans towards the back of the water when it is placed.
877
+ *
878
+ * An anemone is the one rooted thing here with a body, and a body is worth most
879
+ * where a line has stopped reading. Placed evenly, most of them are spent at
880
+ * the front of the picture, where there was already plenty to look at, and the
881
+ * far hills stay what they were. It is a lean rather than a rule: a crown in
882
+ * the foreground is the only one anybody will ever see the tentacles of.
883
+ */
884
+ const CROWN_BACK = 1.7
885
+
886
+ const MIN_SPAN = 1
887
+
888
+ /**
889
+ * How far each kind gives to the water, gathered so a plant can be looked up by
890
+ * what it is.
891
+ *
892
+ * The two skeletons are down at the bottom of it for a reason of their own. A
893
+ * fan and a column are stiff, and the little they are given is not a lean: it
894
+ * is the whole of what says they are alive rather than carved.
895
+ */
896
+ const LEANS: Readonly<Record<PlantKind, number>> = {
897
+ anemone: ANEMONE_LEAN,
898
+ coral: 0,
899
+ fan: FAN_LEAN,
900
+ grass: GRASS_LEAN,
901
+ kelp: KELP_LEAN,
902
+ }
903
+
904
+ /**
905
+ * How much of a plant's height its own strand carries.
906
+ *
907
+ * All of it, except for the fan, whose height is mostly its ribs; see
908
+ * `FAN_STEM`. A coral has no strand at all and the number is never read.
909
+ */
910
+ const STEMS: Readonly<Record<PlantKind, number>> = {
911
+ anemone: 1,
912
+ coral: 0,
913
+ fan: FAN_STEM,
914
+ grass: 1,
915
+ kelp: 1,
916
+ }
917
+
918
+ /**
919
+ * How far a plant wanders before it is worth recutting, in px, by default.
920
+ *
921
+ * A quarter of a pixel, which is where the arithmetic and the eye currently
922
+ * agree on a stroke drawn at a third of the copy's ink. Everything above it is
923
+ * a bed the eye reads as continuous; below it the saving falls away and above
924
+ * it the plants that sway hardest begin to step. A caller with a denser bed, a
925
+ * sharper screen or a slower machine passes its own.
926
+ */
927
+ const TOLERANCE = 0.25
928
+
929
+ /**
930
+ * How much further than its strand's tip the rest of a plant can travel.
931
+ *
932
+ * The judgement is made on the strand, because the strand is the one line whose
933
+ * movement is a closed form of the numbers the sway holds. Everything else a
934
+ * plant is made of rides on it: a leaf hangs off a limb, a rib is cut from the
935
+ * stem, a tentacle is walked out of the mouth. Each of those swings wider than
936
+ * the point it hangs from, by however much its own drawing reaches, so the
937
+ * estimate is scaled by that and `flora.test.ts` holds these numbers to it.
938
+ *
939
+ * They come out the same for everything that bends, which is worth stating
940
+ * because it was not arrived at by assuming it: a leaf, a rib and a mate all
941
+ * reach about as far off their strand as each other, and the bed was measured
942
+ * before these were written down. Coral never moves, so the scale it would need
943
+ * is the one number here that cannot be wrong.
944
+ */
945
+ const SWING: Readonly<Record<PlantKind, number>> = {
946
+ anemone: 2,
947
+ coral: 0,
948
+ fan: 2,
949
+ grass: 2,
950
+ kelp: 2,
951
+ }
952
+
953
+ /**
954
+ * How far the furthest thing on a plant travels on its own clock, as a share of
955
+ * the plant's span, per radian of that clock.
956
+ *
957
+ * Nothing rooted has any of this and one animal has all of it. A stem, a rib
958
+ * and a leaf move because the water moved them, so their whole travel is in the
959
+ * amplitude and `SWING` weighs it. A tentacle curls whether the water is doing
960
+ * anything or not, which is the difference between a plant and an animal down
961
+ * there and is stated at greater length above. So a crown in still water swings
962
+ * its own reach through its own beat while the estimate that only knows about
963
+ * amplitude reads nothing at all, and a bed of anemones holds drawings that are
964
+ * a long way stale.
965
+ */
966
+ const SWEEP: Readonly<Record<PlantKind, number>> = {
967
+ anemone: 0.25,
968
+ coral: 0,
969
+ fan: 0,
970
+ grass: 0,
971
+ kelp: 0,
972
+ }
973
+
974
+ /** How finely each kind's strand is cut. Enough that its bend has no corners. */
975
+ const STEPS: Readonly<Record<PlantKind, number>> = {
976
+ anemone: ANEMONE_STEPS,
977
+ coral: 0,
978
+ fan: FAN_STEPS,
979
+ grass: GRASS_STEPS,
980
+ kelp: KELP_STEPS,
981
+ }
982
+
983
+ /**
984
+ * How thick to draw a plant's strand, in px.
985
+ *
986
+ * Two rules, because two different things are being measured. Kelp and grass
987
+ * are a width in px thinned by how far off they are, which is a stem seen at a
988
+ * distance. A column and a stem are a share of their own height, because what
989
+ * makes either read is that it is stout beside what it carries.
990
+ */
991
+ function girthOf(kind: PlantKind, weed: Weed | null, span: number, depth: number): number {
992
+ if (kind === "anemone") return span * ANEMONE_GIRTH
993
+ if (kind === "fan") return span * FAN_GIRTH
994
+ if (kind === "kelp") return KELP_GIRTH * (weed?.girth ?? 1) * shrunk(depth)
995
+ return GRASS_GIRTH * shrunk(depth)
996
+ }
997
+
998
+ /**
999
+ * One tentacle, as a heading that keeps turning.
1000
+ *
1001
+ * A curl cannot be cut the way a strand is. A strand is a line from a root to a
1002
+ * tip with a bend put through it, and a curl has no tip to aim at: it is a
1003
+ * direction that keeps going round. So this walks it a step at a time, turning
1004
+ * further out as it goes and feeling around as it turns. The feeling is scaled
1005
+ * by how far along the tentacle the step is, for the reason the strand's bend
1006
+ * is: the base is attached to something and the tip is not.
1007
+ */
1008
+ function feeler(
1009
+ x: number,
1010
+ y: number,
1011
+ span: number,
1012
+ open: number,
1013
+ phase: number,
1014
+ steps: number,
1015
+ ): Point[] {
1016
+ const points: Point[] = [{ x, y }]
1017
+ const pace = span / steps
1018
+
1019
+ let atX = x
1020
+ let atY = y
1021
+ for (let step = 1; step <= steps; step++) {
1022
+ const u = step / steps
1023
+ const heading = open * (1 + CROWN_CURL * u) + Math.sin(phase + u * CROWN_WAVES) * CROWN_WAVE * u
1024
+
1025
+ atX += pace * Math.sin(heading)
1026
+ atY -= pace * Math.cos(heading)
1027
+ points.push({ x: atX, y: atY })
1028
+ }
1029
+ return points
1030
+ }
1031
+
1032
+ /**
1033
+ * One tentacle of a crown: how long, how far out, and its own clock.
1034
+ *
1035
+ * Lengths are shares of the column rather than px, which is what lets one crown
1036
+ * be dealt once and cut at whatever size the thing drawing it works in.
1037
+ */
1038
+ export interface Tentacle {
1039
+ /** How much faster or slower than its crown this one works through its own
1040
+ * feeling, so no two are ever at the same point of the same curl. */
1041
+ readonly beat: number
1042
+ readonly own: number
1043
+ /** Where across the mouth it leaves, as a share of the column's height. */
1044
+ readonly shift: number
1045
+ /** How far it opens from upright, in radians. */
1046
+ readonly slant: number
1047
+ /** How long it is, as a share of the column's height. */
1048
+ readonly span: number
1049
+ /**
1050
+ * How finely it is cut, dealt with the crown rather than taken from the span
1051
+ * it is drawn at.
1052
+ *
1053
+ * `crownAt` is cut at 1 by a caller that is about to scale the drawing and at
1054
+ * a real height by one that is not, so the span it is handed says nothing
1055
+ * about how big anybody will see this. What does is the size the crown was
1056
+ * dealt for, which is the size it was grown at and never changes.
1057
+ */
1058
+ readonly steps: number
1059
+ }
1060
+
1061
+ /**
1062
+ * Deal a crown: the tentacles one anemone has, spread evenly across its mouth.
1063
+ *
1064
+ * Evenly, where a clump of grass is splayed at random, because a crown is a
1065
+ * ring of them and a gap in a ring is a gap. What is left to the dice is how
1066
+ * long each one is and where in its own feeling it happens to be, which is
1067
+ * enough that no two agree about anything.
1068
+ *
1069
+ * `drawn` is how many px of column the animal is going to be drawn at, and the
1070
+ * only thing it decides is how many tentacles there are; see `CROWN_CROWDED`.
1071
+ *
1072
+ * Dealt once and cut every step, which is the split `crownAt` is the other half
1073
+ * of. Nothing here reads a clock, so an animal dealt this way is the same
1074
+ * animal however long a scene has been running.
1075
+ */
1076
+ export function crown(seed: number, drawn: number): Tentacle[] {
1077
+ const random = makeRandom(seed)
1078
+ const most = CROWN_LEAST + Math.floor(random() * (CROWN_MOST - CROWN_LEAST + 1))
1079
+ const count = crowded(most, CROWN_FEWEST, drawn, CROWN_CROWDED)
1080
+
1081
+ const tentacles: Tentacle[] = []
1082
+ for (let made = 0; made < count; made++) {
1083
+ const across = count < 2 ? 0 : (made / (count - 1)) * 2 - 1
1084
+
1085
+ const span = CROWN_REACH * (0.75 + random() * 0.55)
1086
+
1087
+ tentacles.push({
1088
+ beat: 0.7 + random() * 0.7,
1089
+ own: random() * Math.PI * 2,
1090
+ shift: across * ANEMONE_GIRTH * 0.5,
1091
+ slant: across * CROWN_OPEN * (0.72 + random() * 0.56),
1092
+ span,
1093
+ steps: cuts(CROWN_STEPS, span * drawn),
1094
+ })
1095
+ }
1096
+ return tentacles
1097
+ }
1098
+
1099
+ /**
1100
+ * How far a crown's furthest tip travels while its own clock turns.
1101
+ *
1102
+ * `span` is the column's height in whatever the caller draws in, and `turned`
1103
+ * is how far the clock has come round since, in radians, so what comes back is
1104
+ * a distance in the caller's own units.
1105
+ *
1106
+ * Exported because a crown is not only the bed's. `reef.ts` grows its anemones
1107
+ * out of `crown` and `crownAt`, which means it has the same question to answer
1108
+ * about whether one has moved far enough to be worth drawing again, and an
1109
+ * estimate written down twice is an estimate that agrees with itself only until
1110
+ * somebody edits one of them.
1111
+ *
1112
+ * It is `SWEEP`'s own number rather than a copy of it, for that reason.
1113
+ */
1114
+ export function crownSwept(span: number, turned: number): number {
1115
+ return span * SWEEP.anemone * turned
1116
+ }
1117
+
1118
+ /**
1119
+ * A crown at a moment, walked out of a mouth you give it.
1120
+ *
1121
+ * `span` is the column's height in whatever the caller draws in, so a crown
1122
+ * dealt once can be cut at 1 for a drawing that is about to be scaled and at
1123
+ * its real height for one that is not. `phase` is the animal's own clock, and
1124
+ * it is an argument rather than something this owns, so a scene can be wound
1125
+ * forward to a moment rather than stepped through every moment before it.
1126
+ */
1127
+ export function crownAt(
1128
+ mouth: Point,
1129
+ tentacles: readonly Tentacle[],
1130
+ span: number,
1131
+ phase: number,
1132
+ ): Blade[] {
1133
+ return tentacles.map((one) =>
1134
+ feeler(
1135
+ mouth.x + one.shift * span,
1136
+ mouth.y,
1137
+ one.span * span,
1138
+ one.slant,
1139
+ phase * one.beat + one.own,
1140
+ one.steps,
1141
+ ),
1142
+ )
1143
+ }
1144
+
1145
+ /** Where something is going to stand: somewhere across the box, at a distance. */
1146
+ interface Spot {
1147
+ readonly depth: number
1148
+ readonly x: number
1149
+ }
1150
+
1151
+ /**
1152
+ * The water as it stood when a plant was last cut.
1153
+ *
1154
+ * Everything a drawing was made from and nothing else, so how far the plant has
1155
+ * wandered since is arithmetic on four numbers. Against the last cut rather
1156
+ * than the last step, because a sum of per-step movements drifts upward on a
1157
+ * plant that is swaying back and forth and would recut one that had returned to
1158
+ * where it started.
1159
+ */
1160
+ interface Drawn {
1161
+ readonly amp: number
1162
+ readonly fright: number
1163
+ readonly own: number
1164
+ /** How tall the column stood, which only a crown pulling itself in changes. */
1165
+ readonly stem: number
1166
+ }
1167
+
1168
+ interface Sway {
1169
+ /** How pulled in it is, 0 open and 1 shut. Nothing but a crown is ever
1170
+ * anything but 0. */
1171
+ readonly fright: number
1172
+ readonly lean: number
1173
+ /**
1174
+ * Whatever else the plant is made of: the rest of a clump of grass, the
1175
+ * tentacles of a crown, the ribs of a fan. Empty for kelp, whose leaves are
1176
+ * cut off its strand where they sit rather than placed around it.
1177
+ */
1178
+ readonly mates: readonly Mate[]
1179
+ readonly own: number
1180
+ readonly rate: number
1181
+ readonly steps: number
1182
+ /** The crown, dealt once. Empty for anything that is not an anemone. */
1183
+ readonly tentacles: readonly Tentacle[]
1184
+ /** Which weed it is. Null for anything that is not one. */
1185
+ readonly weed: Weed | null
1186
+ }
1187
+
1188
+ /**
1189
+ * One line of a plant, and where it leaves whatever it grows from.
1190
+ *
1191
+ * Everything a plant is made of past its leaves is one of these, which is the
1192
+ * point of writing it down: a fork off a kelp, a blade of a clump, a rib of a
1193
+ * fan and a twig off a rib were four pieces of arithmetic saying the same
1194
+ * thing, and they are now one.
1195
+ *
1196
+ * `beat` and `own` are how its clock runs against the plant's, a multiplier and
1197
+ * an offset, and `give` is what share of the plant's amplitude reaches it. A
1198
+ * fan's ribs are stiff because their give is a half; a fork high up a wrack
1199
+ * moves less than the strand under it for the same reason and by its own share.
1200
+ */
1201
+ export interface Limb {
1202
+ readonly beat: number
1203
+ readonly give: number
1204
+ readonly own: number
1205
+ /** Where up the limb it leaves, 0 at that limb's root and 1 at its tip. */
1206
+ readonly seat: number
1207
+ /** How far along the ground it is rooted, for a limb that leaves no limb. */
1208
+ readonly shift: number
1209
+ readonly slant: number
1210
+ readonly span: number
1211
+ /** Which limb it leaves, or -1 for the ground. */
1212
+ readonly stem: number
1213
+ readonly steps: number
1214
+ }
1215
+
1216
+ /**
1217
+ * One leaf, as the shape it is in the frame of the limb it grows on.
1218
+ *
1219
+ * `x` is how far back down the limb the point is and `y` how far out across it,
1220
+ * both in px, so the drawing is finished the moment the plant is sown and all
1221
+ * that is ever done with it again is stand it up wherever the limb has got to.
1222
+ */
1223
+ export interface Leaf {
1224
+ readonly limb: number
1225
+ readonly seat: number
1226
+ readonly shape: readonly Point[]
1227
+ }
1228
+
1229
+ /** What a plant is made of: its limbs, in the order they are drawn. */
1230
+ export interface Frame {
1231
+ readonly leaves: readonly Leaf[]
1232
+ readonly limbs: readonly Limb[]
1233
+ }
1234
+
1235
+ /**
1236
+ * Where the water has a plant's sway got to.
1237
+ *
1238
+ * The whole of what a `Frame` has to be evaluated at, which is the point of it
1239
+ * being two numbers: a renderer that has taken the shape once takes this a
1240
+ * frame, and a bed of six hundred plants is under five kilobytes of it.
1241
+ */
1242
+ export interface Swing {
1243
+ readonly amp: number
1244
+ readonly own: number
1245
+ }
1246
+
1247
+ /**
1248
+ * One more blade in a clump: where it sits, how tall, and which way it leans.
1249
+ *
1250
+ * `slant` is a fixed tilt off upright and `own` its own place in the sway, so
1251
+ * two blades of one clump are never at the same point of the same swing. They
1252
+ * share the clump's amplitude, which is what keeps them a clump.
1253
+ */
1254
+ interface Mate {
1255
+ /** How much faster or slower than its clump this blade works through the
1256
+ * sway, so a clump drifts out of step with itself instead of pulsing. */
1257
+ readonly beat: number
1258
+ readonly own: number
1259
+ /** Where up the strand it leaves, 0 at the root and 1 at the tip. A fan's
1260
+ * ribs climb its stem; everything else leaves from one place. */
1261
+ readonly seat: number
1262
+ readonly shift: number
1263
+ readonly slant: number
1264
+ readonly span: number
1265
+ }
1266
+
1267
+ export function createFlora(options: FloraOptions): Flora {
1268
+ const noise = makeNoise2(options.seed ^ 0x4f10)
1269
+ const random = makeRandom(options.seed ^ 0x6b3c)
1270
+
1271
+ let width = Math.max(MIN_SPAN, options.width)
1272
+ let height = Math.max(MIN_SPAN, options.height)
1273
+ let floor = options.floor
1274
+ let drift = 0
1275
+
1276
+ const plants: Plant[] = []
1277
+ const sways: Sway[] = []
1278
+
1279
+ const tolerance = Math.max(0, options.tolerance ?? TOLERANCE)
1280
+
1281
+ /** Which kinds are drawn here rather than left to whoever asked. */
1282
+ const cutting = new Set<PlantKind>(
1283
+ options.cutting ?? ["anemone", "coral", "fan", "grass", "kelp"],
1284
+ )
1285
+
1286
+ /** What each plant was cut from, or null while it has never been cut. */
1287
+ const drawn: (Drawn | null)[] = []
1288
+
1289
+ /** What each plant is made of, worked out the first time it is drawn. */
1290
+ const frames: (Frame | null)[] = []
1291
+
1292
+ /** Where each plant's sway has got to, kept rather than made every step. */
1293
+ const swinging: { amp: number; own: number }[] = []
1294
+
1295
+ /** The beds the grass grows in, dealt again whenever the box changes. */
1296
+ const beds: Spot[] = []
1297
+
1298
+ /** How tall each strand stands. Off the Plant, because a renderer wants the
1299
+ * points rather than the number they were cut from. */
1300
+ const heights = new Map<number, number>()
1301
+
1302
+ function plant(kind: PlantKind): void {
1303
+ let { depth, x } = where(kind)
1304
+
1305
+ // Asked again rather than pushed aside, since a plant moved off a clearing
1306
+ // lands on its edge and a bed with a rim of weed around a bald spot is more
1307
+ // obviously arranged than one with a gap in it. A handful of tries, because
1308
+ // a clearing is a patch and not most of the floor.
1309
+ for (let again = 0; again < BARE_TRIES && options.bare?.(x, depth); again++) {
1310
+ const spot = where(kind)
1311
+ depth = spot.depth
1312
+ x = spot.x
1313
+ }
1314
+ if (options.bare?.(x, depth)) return
1315
+
1316
+ if (kind === "coral") {
1317
+ plants.push({
1318
+ blades: [],
1319
+ cut: 0,
1320
+ depth,
1321
+ girth: 0,
1322
+ kind,
1323
+ points: [],
1324
+ scale: (CORAL_SMALLEST + random() * (CORAL_LARGEST - CORAL_SMALLEST)) * shrunk(depth),
1325
+ twigs: gathered(REEF[Math.floor(random() * REEF.length)] ?? CORAL),
1326
+ x,
1327
+ y: floor(x, depth),
1328
+ })
1329
+ sways.push({
1330
+ fright: 0,
1331
+ lean: 0,
1332
+ mates: [],
1333
+ own: random() * Math.PI * 2,
1334
+ rate: 0,
1335
+ steps: 0,
1336
+ tentacles: [],
1337
+ weed: null,
1338
+ })
1339
+ drawn.push(null)
1340
+ return
1341
+ }
1342
+
1343
+ const weed = kind === "kelp" ? (WEEDS[Math.floor(random() * WEEDS.length)] ?? FRILL) : null
1344
+ const span = height * stature(kind, weed) * shrunk(depth)
1345
+
1346
+ plants.push({
1347
+ blades: [],
1348
+ cut: 0,
1349
+ depth,
1350
+ girth: girthOf(kind, weed, span, depth),
1351
+ kind,
1352
+ points: [],
1353
+ scale: 1,
1354
+ twigs: [],
1355
+ x,
1356
+ y: floor(x, depth),
1357
+ })
1358
+
1359
+ sways.push({
1360
+ fright: 0,
1361
+ lean: span * LEANS[kind],
1362
+ mates: crop(kind, span),
1363
+ own: random() * Math.PI * 2,
1364
+ rate: SWAY_SLOWEST + random() * (SWAY_FASTEST - SWAY_SLOWEST),
1365
+ steps: cuts(STEPS[kind], span * STEMS[kind]),
1366
+ tentacles: kind === "anemone" ? crown((random() * 0xffff) | 0, span) : [],
1367
+ weed,
1368
+ })
1369
+
1370
+ drawn.push(null)
1371
+ heights.set(plants.length - 1, span)
1372
+ }
1373
+
1374
+ /** How tall one stands, as a share of the box's height. */
1375
+ function stature(kind: PlantKind, weed: Weed | null): number {
1376
+ if (kind === "anemone") return ANEMONE_LEAST + random() * ANEMONE_SPAN
1377
+ if (kind === "fan") return FAN_LEAST + random() * FAN_SPAN
1378
+ if (kind === "kelp") return (KELP_LEAST + random() * KELP_SPAN) * (weed?.stature ?? 1)
1379
+ return GRASS_LEAST + random() * GRASS_SPAN
1380
+ }
1381
+
1382
+ /** Whatever else the plant is made of, placed once and cut every step. */
1383
+ function crop(kind: PlantKind, span: number): Mate[] {
1384
+ if (kind === "fan") return ribs(span)
1385
+ if (kind === "grass") return clump(span)
1386
+ return []
1387
+ }
1388
+
1389
+ /** Where one of these goes: its bed if it has one, the back of the water if
1390
+ * it is worth most there, and anywhere at all otherwise. */
1391
+ function where(kind: PlantKind): Spot {
1392
+ if (kind === "anemone") {
1393
+ return {
1394
+ depth: DEPTH_FAR + random() ** CROWN_BACK * (DEPTH_NEAR - DEPTH_FAR),
1395
+ x: random() * width,
1396
+ }
1397
+ }
1398
+ return kind === "grass" ? sprig() : anywhere()
1399
+ }
1400
+
1401
+ /** Somewhere across the box, at any distance the bed reaches. */
1402
+ function anywhere(): Spot {
1403
+ return { depth: DEPTH_FAR + random() * (DEPTH_NEAR - DEPTH_FAR), x: random() * width }
1404
+ }
1405
+
1406
+ /**
1407
+ * Where one clump of grass lands.
1408
+ *
1409
+ * In a bed, nearer its middle than its edge, which is what two draws added
1410
+ * together buy for nothing. A few land nowhere near one, so that no bed has an
1411
+ * edge the eye can find.
1412
+ */
1413
+ function sprig(): Spot {
1414
+ const bed = beds[Math.floor(random() * beds.length)]
1415
+ if (!bed || random() < MEADOW_STRAY) return anywhere()
1416
+
1417
+ const depth = bed.depth + (random() + random() - 1) * MEADOW_DEEP
1418
+
1419
+ return {
1420
+ depth: Math.min(DEPTH_NEAR, Math.max(DEPTH_FAR, depth)),
1421
+ x: bed.x + (random() + random() - 1) * MEADOW_SPREAD * width,
1422
+ }
1423
+ }
1424
+
1425
+ /**
1426
+ * The blades standing around one root, the tallest of which is drawn already.
1427
+ *
1428
+ * They lean away from the middle rather than at random, because a clump is
1429
+ * one plant opening out and not a handful of separate ones that happen to
1430
+ * have landed together.
1431
+ */
1432
+ function clump(span: number): Mate[] {
1433
+ const mates: Mate[] = []
1434
+ const most = TUFT_LEAST + Math.floor(random() * (TUFT_MOST - TUFT_LEAST + 1))
1435
+ const count = crowded(most, TUFT_FEWEST, span, TUFT_CROWDED)
1436
+
1437
+ for (let made = 0; made < count; made++) {
1438
+ const side = made % 2 === 0 ? 1 : -1
1439
+ const out = (Math.floor(made / 2) + 1) / Math.ceil(count / 2)
1440
+
1441
+ mates.push({
1442
+ beat: 0.82 + random() * 0.4,
1443
+ own: random() * Math.PI * 2,
1444
+ seat: 0,
1445
+ shift: side * out * span * TUFT_ROOT * (0.4 + random() * 0.6),
1446
+ slant: side * out * TUFT_SPLAY * (0.5 + random() * 0.5),
1447
+ span: span * (GRASS_RUNT + random() * (1 - GRASS_RUNT)),
1448
+ })
1449
+ }
1450
+ return mates
1451
+ }
1452
+
1453
+ /**
1454
+ * The ribs of a fan, longest and widest at the foot.
1455
+ *
1456
+ * A gorgonian is broad where it leaves the ground and comes to nothing at the
1457
+ * top, so a rib that leaves low is long and opens near flat, and one that
1458
+ * leaves high is short and nearly upright. Cut the other way round it is a
1459
+ * tree, which is a fine shape and is not this one.
1460
+ */
1461
+ function ribs(span: number): Mate[] {
1462
+ const mates: Mate[] = []
1463
+ const most = RIB_LEAST + Math.floor(random() * (RIB_MOST - RIB_LEAST + 1))
1464
+ const count = crowded(most, RIB_FEWEST, span, RIB_CROWDED)
1465
+
1466
+ for (let made = 0; made < count; made++) {
1467
+ const side = made % 2 === 0 ? 1 : -1
1468
+ const up = (Math.floor(made / 2) + 1) / Math.ceil(count / 2)
1469
+
1470
+ mates.push({
1471
+ beat: 0.9 + random() * 0.25,
1472
+ own: random() * Math.PI * 2,
1473
+ seat: Math.min(1, RIB_SEAT + up * (1 - RIB_SEAT) * (0.75 + random() * 0.35)),
1474
+ shift: 0,
1475
+ slant: side * RIB_OPEN * (1 - up * 0.55),
1476
+ span: span * RIB_REACH * (1 - up * 0.18),
1477
+ })
1478
+ }
1479
+ return mates
1480
+ }
1481
+
1482
+ function sow(): void {
1483
+ plants.length = 0
1484
+ sways.length = 0
1485
+ beds.length = 0
1486
+ drawn.length = 0
1487
+ frames.length = 0
1488
+ swinging.length = 0
1489
+ heights.clear()
1490
+
1491
+ for (let made = 0; made < Math.max(1, Math.round(width / MEADOW_EVERY)); made++) {
1492
+ beds.push(anywhere())
1493
+ }
1494
+
1495
+ for (let made = 0; made < Math.max(0, options.anemones ?? 0); made++) plant("anemone")
1496
+ for (let made = 0; made < Math.max(0, options.corals ?? 0); made++) plant("coral")
1497
+ for (let made = 0; made < Math.max(0, options.fans ?? 0); made++) plant("fan")
1498
+ for (let made = 0; made < Math.max(0, options.grasses ?? 0); made++) plant("grass")
1499
+ for (let made = 0; made < Math.max(0, options.kelps ?? 0); made++) plant("kelp")
1500
+ }
1501
+
1502
+ /**
1503
+ * Recut one strand where the water currently has it.
1504
+ *
1505
+ * The bend is the site's, and the amplitude is this file's own business: a
1506
+ * share of it comes off the shared field at the plant's own position, so a
1507
+ * stretch of bed leans together, and the rest is the plant's own slow swing.
1508
+ */
1509
+ function bend(at: number, field: Noise2): void {
1510
+ const one = plants[at]
1511
+ const sway = sways[at]
1512
+ const span = heights.get(at)
1513
+ if (!one || !sway || span == null || one.kind === "coral") return
1514
+
1515
+ const current = field((one.x / width) * FIELD_CELLS + drift, drift)
1516
+ const own = Math.sin(sway.own)
1517
+ const amp = sway.lean * (current * CURRENT_SHARE + own * (1 - CURRENT_SHARE))
1518
+
1519
+ // Written down whether or not the plant is worth cutting again, because a
1520
+ // renderer bending the shape itself wants where the water is now and has no
1521
+ // use at all for how far it has come since the last drawing.
1522
+ const swing = swinging[at]
1523
+ if (swing) {
1524
+ swing.amp = amp
1525
+ swing.own = sway.own
1526
+ } else {
1527
+ swinging[at] = { amp, own: sway.own }
1528
+ }
1529
+
1530
+ if (!cutting.has(one.kind)) return
1531
+
1532
+ // A frightened crown hunches: the column squats and the tentacles come in.
1533
+ // Both off the one number, because they are one movement.
1534
+ const shy = one.kind === "anemone" ? 1 - sway.fright * COLUMN_SQUAT : 1
1535
+ const stem = span * STEMS[one.kind] * shy
1536
+ const was = drawn[at]
1537
+ if (was && stir(one.kind, span, was, amp, sway.fright, sway.own, stem) < tolerance) return
1538
+
1539
+ drawn[at] = { amp, fright: sway.fright, own: sway.own, stem }
1540
+ one.cut++
1541
+
1542
+ if (one.kind === "anemone") {
1543
+ // A crown is walked rather than cut, so it is the one thing here that is
1544
+ // not a shape the water bends: it is a heading that keeps turning, and
1545
+ // where it has turned to is the clock itself. The column is a strand like
1546
+ // any other, and the crown leaves from wherever the water has left its
1547
+ // mouth. That is the whole of what the two have to say to each other: the
1548
+ // crown is not reading the current, it is riding on something that did.
1549
+ const points = strand(one.x, one.y, stem, sway.own, amp, 0, sway.steps)
1550
+ const mouth = points[points.length - 1]
1551
+ const out = span * (1 - sway.fright * CROWN_PULL)
1552
+
1553
+ one.points = points
1554
+ one.blades = mouth ? crownAt(mouth, sway.tentacles, out, sway.own) : []
1555
+ return
1556
+ }
1557
+
1558
+ cutFrom(one, framed(at), amp, sway.own)
1559
+ }
1560
+
1561
+ /**
1562
+ * What a plant is made of, worked out once and kept.
1563
+ *
1564
+ * Everything here was already fixed when the plant was sown: where a fork
1565
+ * leaves, how many leaves a weed that tall carries, how far out a rib opens,
1566
+ * how long a leaf is and what shape. None of it is anything the water has a
1567
+ * say in. It was being worked out again on every plant on every frame it
1568
+ * moved, which is most of what a bed cost to draw.
1569
+ *
1570
+ * Splitting it out buys a second thing, and the second thing is the larger.
1571
+ * A plant that can say what it is made of can be handed to a renderer that
1572
+ * bends it somewhere else: the shape goes over once and the water sends two
1573
+ * numbers a frame after that. A plant that can only hand over the points it
1574
+ * happens to be at this instant has to hand them all over again every time
1575
+ * it moves.
1576
+ */
1577
+ function framed(at: number): Frame {
1578
+ const held = frames[at]
1579
+ if (held) return held
1580
+
1581
+ const made = grown(at)
1582
+ frames[at] = made
1583
+ return made
1584
+ }
1585
+
1586
+ /** Where up a line something leaves it, as one of the points it was cut at. */
1587
+ const seated = (seat: number, steps: number): number =>
1588
+ Math.min(steps, Math.max(0, Math.round(seat * steps))) / steps
1589
+
1590
+ /**
1591
+ * The lines a plant is made of, and the leaves hanging off them.
1592
+ *
1593
+ * A limb knows the limb it leaves and where up it, so the whole plant is a
1594
+ * short tree and cutting it is one pass down the list. The order the limbs
1595
+ * are written in is the order they are drawn in, because it is also the order
1596
+ * they were in when this was three branches of one function.
1597
+ */
1598
+ function grown(at: number): Frame {
1599
+ const one = plants[at]
1600
+ const sway = sways[at]
1601
+ const span = heights.get(at)
1602
+ if (!one || !sway || span == null) return { leaves: [], limbs: [] }
1603
+
1604
+ const limbs: Limb[] = [
1605
+ {
1606
+ beat: 1,
1607
+ give: 1,
1608
+ own: 0,
1609
+ seat: 0,
1610
+ shift: 0,
1611
+ slant: 0,
1612
+ span: span * STEMS[one.kind],
1613
+ stem: -1,
1614
+ steps: sway.steps,
1615
+ },
1616
+ ]
1617
+
1618
+ if (one.kind === "grass") {
1619
+ for (const mate of sway.mates) {
1620
+ limbs.push({
1621
+ beat: mate.beat,
1622
+ give: 1,
1623
+ own: mate.own,
1624
+ seat: 0,
1625
+ shift: mate.shift,
1626
+ slant: mate.slant,
1627
+ span: mate.span,
1628
+ stem: -1,
1629
+ steps: cuts(sway.steps, mate.span),
1630
+ })
1631
+ }
1632
+ return { leaves: [], limbs }
1633
+ }
1634
+
1635
+ if (one.kind === "fan") {
1636
+ for (const mate of sway.mates) {
1637
+ const rib = limbs.length
1638
+ const steps = cuts(FAN_STEPS, mate.span)
1639
+
1640
+ limbs.push({
1641
+ beat: mate.beat,
1642
+ give: RIB_GIVE,
1643
+ own: mate.own,
1644
+ seat: seated(mate.seat, sway.steps),
1645
+ shift: 0,
1646
+ slant: mate.slant,
1647
+ span: mate.span,
1648
+ stem: 0,
1649
+ steps,
1650
+ })
1651
+
1652
+ if (mate.span < RIB_TWIGGED) continue
1653
+
1654
+ limbs.push({
1655
+ beat: 1,
1656
+ give: RIB_GIVE,
1657
+ own: mate.own,
1658
+ seat: Math.floor((steps + 1) / 2) / steps,
1659
+ shift: 0,
1660
+ slant: mate.slant * RIB_SPLAY,
1661
+ span: mate.span * RIB_TWIG,
1662
+ stem: rib,
1663
+ steps: 2,
1664
+ })
1665
+ }
1666
+ return { leaves: [], limbs }
1667
+ }
1668
+
1669
+ const weed = sway.weed ?? FRILL
1670
+
1671
+ weed.forks.forEach((up, made) => {
1672
+ limbs.push({
1673
+ beat: 1,
1674
+ give: 1 - up,
1675
+ own: up * Math.PI,
1676
+ seat: seated(up, sway.steps),
1677
+ shift: 0,
1678
+ slant: (made % 2 === 0 ? 1 : -1) * FORK_SPLAY,
1679
+ span: span * (1 - up) * FORK_REACH,
1680
+ stem: 0,
1681
+ steps: Math.max(FEWEST_STEPS, Math.round(sway.steps * (1 - up))),
1682
+ })
1683
+ })
1684
+
1685
+ return { leaves: leafage(weed, limbs, span), limbs }
1686
+ }
1687
+
1688
+ /**
1689
+ * Where the leaves sit on a plant's limbs, and the shape each of them is.
1690
+ *
1691
+ * The shape is written down in the frame of the limb it grows on: how far
1692
+ * back down the limb, and how far out across it. What that saves is the whole
1693
+ * of a leaf's arithmetic, since a leaf is the same drawing whatever the water
1694
+ * is doing and only the frame it hangs in moves.
1695
+ *
1696
+ * A leaf's length is taken off the limb's own reach rather than off the
1697
+ * length the limb happens to be bent to. It is the argument the leaf count
1698
+ * already makes: a drawing dealt when the plant was sown must not gain and
1699
+ * lose anything as the plant sways.
1700
+ */
1701
+ function leafage(weed: Weed, limbs: readonly Limb[], span: number): Leaf[] {
1702
+ const leaves: Leaf[] = []
1703
+ const leafy = crowded(weed.blades, LEAF_FEWEST, span, LEAF_CROWDED)
1704
+ const each = Math.max(1, Math.round(leafy / limbs.length))
1705
+ const bends = cuts(BLADE_STEPS, span * weed.span)
1706
+
1707
+ limbs.forEach((limb, on) => {
1708
+ for (let made = 0; made < each; made++) {
1709
+ const seat = weed.seat + (made / each) * (1 - weed.seat)
1710
+ const side = made % 2 === 0 ? 1 : -1
1711
+
1712
+ // Shorter the higher up it sits, which is what a plant that has been
1713
+ // growing from the bottom for a season looks like.
1714
+ const long = limb.span * weed.span * (1 - weed.taper * seat)
1715
+
1716
+ const shape: Point[] = []
1717
+ for (let step = 0; step <= bends; step++) {
1718
+ const u = step / bends
1719
+ const out = Math.sin(u * Math.PI * 0.62) * (1 - u * 0.72) * weed.flare
1720
+ shape.push({ x: u * weed.trail * long, y: out * long * side })
1721
+ }
1722
+
1723
+ leaves.push({ limb: on, seat: seated(seat, limb.steps), shape })
1724
+ }
1725
+ })
1726
+
1727
+ return leaves
1728
+ }
1729
+
1730
+ /**
1731
+ * Cut a plant where the water has it: every limb, and then every leaf.
1732
+ *
1733
+ * One pass, because a limb never leaves a limb written after it. What a leaf
1734
+ * needs is the two points its own limb was cut at where it sits, since down
1735
+ * the limb and out across it are both read off the line as drawn: that is the
1736
+ * whole of why a leaf never crosses what it grows from, however far the plant
1737
+ * is leaning.
1738
+ */
1739
+ function cutFrom(one: Plant, frame: Frame, amp: number, own: number): void {
1740
+ const lines: Point[][] = []
1741
+
1742
+ for (const limb of frame.limbs) {
1743
+ const root =
1744
+ limb.stem < 0 ? { x: one.x + limb.shift, y: one.y } : at(lines[limb.stem], limb.seat)
1745
+
1746
+ lines.push(
1747
+ strand(
1748
+ root.x,
1749
+ root.y,
1750
+ limb.span,
1751
+ own * limb.beat + limb.own,
1752
+ amp * limb.give,
1753
+ limb.slant,
1754
+ limb.steps,
1755
+ ),
1756
+ )
1757
+ }
1758
+
1759
+ const blades: Blade[] = lines.slice(1)
1760
+
1761
+ for (const leaf of frame.leaves) {
1762
+ const line = lines[leaf.limb]
1763
+ if (!line) continue
1764
+
1765
+ const up = Math.round(leaf.seat * (line.length - 1))
1766
+ const on = line[Math.min(line.length - 1, up)]
1767
+ const under = line[Math.max(0, up - 1)]
1768
+ if (!on || !under) continue
1769
+
1770
+ const runX = on.x - under.x
1771
+ const runY = on.y - under.y
1772
+ const run = Math.hypot(runX, runY) || 1
1773
+
1774
+ const trailX = -runX / run
1775
+ const trailY = -runY / run
1776
+ const blade: Point[] = []
1777
+ for (const step of leaf.shape) {
1778
+ blade.push({
1779
+ x: on.x + trailX * step.x + trailY * step.y,
1780
+ y: on.y + trailY * step.x - trailX * step.y,
1781
+ })
1782
+ }
1783
+ blades.push(blade)
1784
+ }
1785
+
1786
+ one.points = lines[0] ?? []
1787
+ one.blades = blades
1788
+ }
1789
+
1790
+ /** The point a line was cut at, this far up it. */
1791
+ function at(line: Point[] | undefined, seat: number): Point {
1792
+ if (!line || line.length === 0) return { x: 0, y: 0 }
1793
+ const up = Math.round(seat * (line.length - 1))
1794
+ return line[Math.min(line.length - 1, Math.max(0, up))] ?? { x: 0, y: 0 }
1795
+ }
1796
+
1797
+ /**
1798
+ * One line from a root, bent by the water and tilted off upright.
1799
+ *
1800
+ * The bend is the site's `Marks.strand/3`: the `* t` is what plants the base
1801
+ * and lets only the tip travel. The slant is this file's own, and it is a
1802
+ * whole rotation rather than a nudge to the top, so a blade leaning out of a
1803
+ * clump leaves the ground already leaning instead of curving away halfway up.
1804
+ */
1805
+ function strand(
1806
+ x: number,
1807
+ y: number,
1808
+ span: number,
1809
+ phase: number,
1810
+ amp: number,
1811
+ slant: number,
1812
+ steps: number,
1813
+ ): Point[] {
1814
+ const lean = Math.sin(slant)
1815
+ const rise = Math.cos(slant)
1816
+
1817
+ const points: Point[] = []
1818
+ for (let step = 0; step <= steps; step++) {
1819
+ const t = step / steps
1820
+ points.push({
1821
+ x: x + span * t * lean + amp * Math.sin(phase + t * 2.4) * t,
1822
+ y: y - span * t * rise,
1823
+ })
1824
+ }
1825
+ return points
1826
+ }
1827
+
1828
+ /**
1829
+ * How pulled in this crown should be, given what is in the water.
1830
+ *
1831
+ * Bigger than it is and nearly on top of it; see `CROWN_MINDS`. Nothing here
1832
+ * is told anything: the bed reads the register once a step and every crown in
1833
+ * it makes up its own mind, which is why two anemones a hand apart do not
1834
+ * shut together.
1835
+ */
1836
+ function scared(one: Plant, span: number, water: readonly Presence[]): number {
1837
+ let worst = 0
1838
+
1839
+ for (const thing of water) {
1840
+ if (thing.size < span * CROWN_MINDS) continue
1841
+
1842
+ const reach = thing.size * CROWN_NOTICE
1843
+ const away = Math.hypot(one.x - thing.x, one.y - thing.y)
1844
+ if (away >= reach) continue
1845
+
1846
+ // Weighed by how nearly abreast the two are, because a screen has two
1847
+ // distances and this water has three: something crossing at the glass is
1848
+ // not on top of a crown rooted in the murk, however neatly they line up.
1849
+ worst = Math.max(worst, (1 - away / reach) * abreast(one.depth, thing.depth))
1850
+ }
1851
+
1852
+ return worst
1853
+ }
1854
+
1855
+ /** Put every clock forward, which is all a stretch of time does to a bed. */
1856
+ function carry(seconds: number, water: readonly Presence[]): void {
1857
+ drift += DRIFT * seconds
1858
+
1859
+ for (let at = 0; at < sways.length; at++) {
1860
+ const sway = sways[at]
1861
+ const one = plants[at]
1862
+ if (!sway || !one) continue
1863
+
1864
+ const shy = one.kind === "anemone" ? scared(one, heights.get(at) ?? 0, water) : 0
1865
+ const fright = Math.max(0, Math.max(sway.fright - seconds / PULL_FADE, shy))
1866
+
1867
+ // Reassigning through the array keeps `Sway` readable as a record.
1868
+ if (sway.rate > 0 || fright !== sway.fright) {
1869
+ sways[at] = { ...sway, fright, own: sway.own + sway.rate * seconds }
1870
+ }
1871
+ }
1872
+ }
1873
+
1874
+ /**
1875
+ * How far a point on a plant can have travelled since it was last cut.
1876
+ *
1877
+ * A bound, and the whole worth of it is that it costs four subtractions where
1878
+ * finding out for certain costs the cut. The tip of a strand sits at
1879
+ * `amp * sin(phase + 2.4)`, so a change in the amplitude moves it by no more
1880
+ * than that change, and a change in the phase by no more than the amplitude
1881
+ * times the angle turned. Every point below the tip moves less, because the
1882
+ * `* t` that plants the root shortens all of it.
1883
+ *
1884
+ * The column's height is the third term and a crown's grip on its own
1885
+ * tentacles is the fourth, since those are the other two numbers a drawing is
1886
+ * made from. `SWING` carries whatever the plant has hung off its strand and
1887
+ * `SWEEP` carries whatever moves without the water moving it.
1888
+ *
1889
+ * It runs high rather than low, deliberately. Over-reporting spends a cut
1890
+ * that was not needed and under-reporting leaves a stale drawing on the
1891
+ * screen, and only one of those is visible.
1892
+ */
1893
+ function stir(
1894
+ kind: PlantKind,
1895
+ span: number,
1896
+ was: Drawn,
1897
+ amp: number,
1898
+ fright: number,
1899
+ own: number,
1900
+ stem: number,
1901
+ ): number {
1902
+ const reach = Math.max(Math.abs(amp), Math.abs(was.amp))
1903
+ const turned = Math.abs(own - was.own)
1904
+
1905
+ return (
1906
+ (Math.abs(amp - was.amp) + reach * turned) * SWING[kind] +
1907
+ span * SWEEP[kind] * turned +
1908
+ Math.abs(stem - was.stem) +
1909
+ Math.abs(fright - was.fright) * span * CROWN_PULL
1910
+ )
1911
+ }
1912
+
1913
+ /** Recut every plant the water has moved far enough to be worth recutting. */
1914
+ function recut(): void {
1915
+ for (let at = 0; at < plants.length; at++) bend(at, noise)
1916
+ }
1917
+
1918
+ function advance(seconds: number): void {
1919
+ carry(Math.min(Math.max(seconds, 0), 0.1), options.about?.() ?? [])
1920
+ recut()
1921
+ }
1922
+
1923
+ sow()
1924
+ advance(0)
1925
+
1926
+ return {
1927
+ madeOf(at) {
1928
+ const one = plants[at]
1929
+ if (!one || one.kind === "coral" || one.kind === "anemone") return null
1930
+ return framed(at)
1931
+ },
1932
+
1933
+ plants,
1934
+
1935
+ resize(nextWidth, nextHeight, nextFloor) {
1936
+ width = Math.max(MIN_SPAN, nextWidth)
1937
+ height = Math.max(MIN_SPAN, nextHeight)
1938
+ floor = nextFloor
1939
+ sow()
1940
+ advance(0)
1941
+ },
1942
+
1943
+ step: advance,
1944
+
1945
+ swinging,
1946
+
1947
+ wind(seconds) {
1948
+ // Wound through empty water, whatever is in it now. A wind is how a scene
1949
+ // opens somewhere other than its starting line, and nothing was in the
1950
+ // water while nobody was watching it.
1951
+ carry(Math.max(seconds, 0), [])
1952
+ recut()
1953
+ },
1954
+ }
1955
+ }