@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/reef.ts ADDED
@@ -0,0 +1,945 @@
1
+ /**
2
+ * A reef: one mound of rock, and everything that has taken a place on it.
3
+ *
4
+ * The simulation behind a reef, kept free of the DOM the way `flora.ts` is, and
5
+ * built on the same drawing contract: every coral on it is a handful of
6
+ * tapering strokes from one base, so anything that can already draw a `Twig`
7
+ * can draw this without being told what a reef is.
8
+ *
9
+ * ## Why a reef is not more plants
10
+ *
11
+ * `flora.ts` sows the bed: corals scattered along the sand, each standing on
12
+ * its own and none of them aware of the others. That is what a bed looks like,
13
+ * and it is not what a reef looks like. A reef is a place. It has a body of its
14
+ * own that stands up out of the ground, it has a top and a face and a foot, and
15
+ * every living thing on it is somewhere particular on that body, because where
16
+ * you are on a reef decides whether you can live there at all.
17
+ *
18
+ * So the mound comes first and the corals are placed against it. Each one is
19
+ * handed the rock and asks it what settles where anything grows: how high am I,
20
+ * and how hard does the water run here. A whip wants the swept crest and a
21
+ * leather coral wants the sheltered foot, and neither of them is put there by
22
+ * this file. `SORTS` says what each kind can live with, and the coral finds its
23
+ * own spot or does not grow.
24
+ *
25
+ * ## Health is how many kinds, not how many corals
26
+ *
27
+ * A reef covered in one coral is a reef in trouble, whatever it looks like from
28
+ * a distance. What a healthy one has is a great many kinds crowded together,
29
+ * each holding the ground it can hold, and that is also the only thing that
30
+ * makes a drawing of one read as alive: a hundred copies of one silhouette is
31
+ * wallpaper at any density. Every kind in `SORTS` gets first refusal on the
32
+ * rock before any kind is placed twice, and `Reef.health` is the share of them
33
+ * that found somewhere. Nearly every reef comes out whole. A cramped one does
34
+ * not, and the figure is there to say so rather than to be tuned away.
35
+ *
36
+ * ## What sits in front of the rock
37
+ *
38
+ * A mound drawn in profile has a skyline, and a coral placed on the skyline is
39
+ * a coral on the top. Real ones are all over the front of it as well, which in
40
+ * this projection means standing inside the silhouette and nearer the glass.
41
+ * That is `Sort.cling`: fans and whips take the face, brains and tables take
42
+ * the top, and the reef comes out with a front and a back rather than a fringe
43
+ * along one line.
44
+ *
45
+ * ## The anemone is the bed's animal, standing on rock
46
+ *
47
+ * There is one anemone in this ornament and `flora.ts` owns it. It sows its own
48
+ * on open sand; the reef places the same creature on the mound and hands a
49
+ * clownfish the address. Two drawings of one animal in one picture is the
50
+ * repeat this codebase argues against everywhere else, and the reader who sees
51
+ * both would notice they were not the same creature before working out why.
52
+ *
53
+ * What that costs here is a second way of being drawn. Coral arrives as a
54
+ * stencil, because a skeleton is a shape that holds. An anemone arrives as a
55
+ * stalk and a crown of tentacles, recut every step, because it is an animal and
56
+ * an animal that held still would be a dead one.
57
+ *
58
+ * ## A coral takes its size from its own distance
59
+ *
60
+ * The mound keeps its size whatever distance it stands at, because it is ground
61
+ * and ground does not shrink: a hill at the back of a seabed is not drawn
62
+ * smaller, it is drawn higher up the box and hazier, and `seabed.ts` does that
63
+ * already. What stands on the ground is the other thing entirely, and it has to
64
+ * shrink or the picture has no distance in it.
65
+ *
66
+ * So a coral is sized by the mound it grew on and then by how far back it is
67
+ * standing, straight through, the way `flora.ts` sizes the corals it sows on
68
+ * the sand. Anything softer than straight through is what put a reef at the
69
+ * back of the water with corals on it the size of the ones at the front, which
70
+ * is the one fault in a scene that everybody notices and nobody can name.
71
+ *
72
+ * ## Nothing hard ever moves
73
+ *
74
+ * Stony coral is a skeleton, so it holds whatever shape it grew into, and the
75
+ * step here skips it entirely. What moves is the soft half: fans, whips, soft
76
+ * corals and the anemones, all leaning together as the swell crosses the reef
77
+ * and each on its own phase, so the crest breathes rather than pulsing.
78
+ *
79
+ * Everything is seeded, so the same seed builds the same reef twice.
80
+ */
81
+
82
+ import {
83
+ type Blade,
84
+ crown,
85
+ crownAt,
86
+ crownSwept,
87
+ gathered,
88
+ type Tentacle,
89
+ type Twig,
90
+ } from "./flora.ts"
91
+ import { makeNoise2, makeRandom, stir } from "./perlin.ts"
92
+ import type { Point } from "./seabed.ts"
93
+
94
+ export type HeadKind =
95
+ | "anemone"
96
+ | "brain"
97
+ | "elkhorn"
98
+ | "fan"
99
+ | "leather"
100
+ | "plate"
101
+ | "staghorn"
102
+ | "table"
103
+ | "tube"
104
+ | "whip"
105
+
106
+ /**
107
+ * One thing growing on the reef, wherever it found room.
108
+ *
109
+ * The same shape of fact as a `Plant`, on purpose. A renderer strokes `twigs`
110
+ * at `scale` about `(x, y)`, turned by `lean` and then by `bend`, and needs to
111
+ * know nothing else about what it is drawing.
112
+ */
113
+ export interface Head {
114
+ /** How far it is leaning in the swell, in radians. */
115
+ bend: number
116
+ /**
117
+ * Whatever else it is made of. Empty for a stencil.
118
+ *
119
+ * A `Plant`'s field, and the same idea: a renderer strokes every line a head
120
+ * hands it at the head's own `girth` and has no business knowing which of
121
+ * them is a tentacle. What has one of these is an animal rather than a
122
+ * skeleton, and it is drawn the way the bed's are rather than being stamped.
123
+ */
124
+ blades: Blade[]
125
+ /**
126
+ * How many times this head has been drawn again.
127
+ *
128
+ * The only thing a renderer has to watch, and `Plant.cut` for the rock. A
129
+ * skeleton bumps it once and never again, which is most of what grows here:
130
+ * the kinds with no `sway` are stone and have nothing that moves at all.
131
+ */
132
+ cut: number
133
+ /** 0 is as far off as this water goes, 1 is up against the glass. */
134
+ readonly depth: number
135
+ /** How thick to stroke `points` and `blades`, in the drawing's own units. */
136
+ readonly girth: number
137
+ readonly kind: HeadKind
138
+ /** Its own phase in the swell, so neighbours never breathe together. */
139
+ readonly lane: number
140
+ /** How far off upright it grew, in radians. Nothing on a reef is plumb. */
141
+ readonly lean: number
142
+ /** Its stalk, root to mouth, for a head that has one. Empty for a stencil. */
143
+ readonly points: readonly Point[]
144
+ /** px per unit of its drawing. */
145
+ readonly scale: number
146
+ /** How wide it stands, in px. What its neighbours were asked about. */
147
+ readonly span: number
148
+ /** Its drawing, from `SHAPES`. */
149
+ readonly twigs: readonly Twig[]
150
+ readonly x: number
151
+ readonly y: number
152
+ }
153
+
154
+ export interface Reef {
155
+ /** The rock's own skyline, left to right, for a renderer to close and fill. */
156
+ readonly crest: readonly Point[]
157
+ /** How far back the mound stands. Everything on it is at least this far. */
158
+ readonly depth: number
159
+ readonly heads: readonly Head[]
160
+ /** The share of the kinds there are that found somewhere to live. */
161
+ readonly health: number
162
+ /**
163
+ * Whether a point is over this reef, clear of the rock, and near enough to
164
+ * count as being on it.
165
+ *
166
+ * The question anything that lives here asks before it goes anywhere. It is
167
+ * the reef's to answer rather than the animal's, because the reef is the one
168
+ * that knows where it ends. `depth` is how far forward the asker is, and it
169
+ * matters for the reason `surfaceAt` gives.
170
+ */
171
+ holds(x: number, y: number, depth?: number): boolean
172
+ resize(width: number, height: number, floor: (x: number, depth?: number) => number): void
173
+ /** Where the rock reaches, left and right, in px. */
174
+ readonly span: readonly [number, number]
175
+ step(seconds: number): void
176
+ /**
177
+ * The top of the rock at x, or the ground where the reef does not reach.
178
+ *
179
+ * `depth` is how far forward the thing standing there is, and it is not a
180
+ * decoration: the mound has a near face that falls away towards the glass, so
181
+ * the rock is lower in front of the crest than at it, and anything standing
182
+ * further forward than the reef itself has less of it overhead. It defaults
183
+ * to the reef's own distance, which is the skyline `crest` is cut from.
184
+ */
185
+ surfaceAt(x: number, depth?: number): number
186
+ /** A long stretch of swell in one go, for a caller winding a scene forward. */
187
+ wind(seconds: number): void
188
+ }
189
+
190
+ export interface ReefOptions {
191
+ /** The ground at x, at a given distance; `seabed.floorAt`. */
192
+ floor: (x: number, depth?: number) => number
193
+ /** How many corals to try to grow, once every kind has had its one. */
194
+ heads?: number
195
+ height: number
196
+ seed: number
197
+ /**
198
+ * How far a head may move from its last drawing before it is drawn again, in
199
+ * px.
200
+ *
201
+ * `FloraOptions.tolerance` for the rock, and the same bargain. Most of what
202
+ * grows here is a skeleton that never moves, so most of the reef costs one
203
+ * comparison a step once it has been drawn once; what sways pays for the
204
+ * frames it is actually using. At nothing every head is recut on every step,
205
+ * which is how this behaved before.
206
+ */
207
+ tolerance?: number
208
+ width: number
209
+ }
210
+
211
+ /**
212
+ * What each kind of coral needs, and what it does with what it gets.
213
+ *
214
+ * The whole of the placement is in this table. There is one routine that grows
215
+ * a coral and it reads these numbers; a kind that wanted its own branch would
216
+ * be a second reef wearing the first one's interface.
217
+ *
218
+ * What decides everything is `perch` and `flow`. A reef is banded, and the
219
+ * bands are not a matter of taste: the crest is swept by every wave that
220
+ * arrives and only the strong-boned and the flexible hold there, while the foot
221
+ * is still and shaded and belongs to the soft and the spreading. Get those
222
+ * right and the reef looks studied from across a room without any one coral
223
+ * being worth looking at.
224
+ */
225
+ interface Sort {
226
+ /**
227
+ * How far down the face it stands, as a share of the mound's rise.
228
+ *
229
+ * Taken against how much rock there is where it landed, so it comes to
230
+ * nothing out on the apron. Only a face can be clung to, and the ground
231
+ * beside a reef is not one: a sponge out there is standing on the flat, and
232
+ * one that had kept its share of the face would be standing in the water
233
+ * below the reef with nothing under it.
234
+ */
235
+ readonly cling: number
236
+ /** The run of water it will take, still to swept. */
237
+ readonly flow: readonly [number, number]
238
+ /** The band of rock it will take, foot to crest. */
239
+ readonly perch: readonly [number, number]
240
+ /** How much clear water it wants around it, against its own width. */
241
+ readonly room: number
242
+ /** How big it grows against what `GROWTH` allows, smallest to largest. */
243
+ readonly size: readonly [number, number]
244
+ /** How far it leans in the swell, in radians. A skeleton is 0. */
245
+ readonly sway: number
246
+ /** How many of it there are, once every kind has its one. */
247
+ readonly weight: number
248
+ /** How wide its drawing is, in its own units. */
249
+ readonly width: number
250
+ }
251
+
252
+ const SORTS: Readonly<Record<HeadKind, Sort>> = {
253
+ /** Soft, sheltered, and the only thing here anything lives inside. */
254
+ anemone: {
255
+ cling: 0.3,
256
+ flow: [0, 0.78],
257
+ perch: [0.15, 0.9],
258
+ room: 1.6,
259
+ size: [1, 1.5],
260
+ sway: 0.07,
261
+ weight: 3,
262
+ width: 28,
263
+ },
264
+ /** A dome that holds anywhere, and the one shape here with a body. */
265
+ brain: {
266
+ cling: 0.15,
267
+ flow: [0, 1],
268
+ perch: [0.1, 1],
269
+ room: 1.1,
270
+ size: [0.6, 1.5],
271
+ sway: 0,
272
+ weight: 5,
273
+ width: 30,
274
+ },
275
+ /** Broad blades into the surge, which is the one place it beats anything. */
276
+ elkhorn: {
277
+ cling: 0,
278
+ flow: [0.55, 1],
279
+ perch: [0.6, 1],
280
+ room: 1.4,
281
+ size: [0.7, 1.2],
282
+ sway: 0,
283
+ weight: 3,
284
+ width: 42,
285
+ },
286
+ /** A mesh held across the current, so it stands where the current runs. */
287
+ fan: {
288
+ cling: 0.55,
289
+ flow: [0.45, 1],
290
+ perch: [0.3, 0.95],
291
+ room: 1.3,
292
+ size: [0.6, 1.3],
293
+ sway: 0.1,
294
+ weight: 4,
295
+ width: 34,
296
+ },
297
+ /** Soft and lobed, down at the foot where nothing is pulling at it. */
298
+ leather: {
299
+ cling: 0.4,
300
+ flow: [0, 0.45],
301
+ perch: [0, 0.5],
302
+ room: 1.2,
303
+ size: [0.7, 1.2],
304
+ sway: 0.07,
305
+ weight: 3,
306
+ width: 26,
307
+ },
308
+ /** Spread flat to catch what light gets down there. */
309
+ plate: {
310
+ cling: 0.35,
311
+ flow: [0, 0.55],
312
+ perch: [0, 0.55],
313
+ room: 1.5,
314
+ size: [0.7, 1.3],
315
+ sway: 0,
316
+ weight: 3,
317
+ width: 46,
318
+ },
319
+ /** Fine branches, which snap in surge, so never at the crest. */
320
+ staghorn: {
321
+ cling: 0.1,
322
+ flow: [0, 0.6],
323
+ perch: [0.35, 0.9],
324
+ room: 1.3,
325
+ size: [0.6, 1.1],
326
+ sway: 0,
327
+ weight: 4,
328
+ width: 36,
329
+ },
330
+ /** A floor of its own, held up into the light at the top of the mound. */
331
+ table: {
332
+ cling: 0,
333
+ flow: [0.2, 0.9],
334
+ perch: [0.7, 1],
335
+ room: 1.7,
336
+ size: [0.7, 1.2],
337
+ sway: 0,
338
+ weight: 2,
339
+ width: 46,
340
+ },
341
+ /** Sponge, not coral, and it takes the shaded foot nothing else wants. */
342
+ tube: {
343
+ cling: 0.5,
344
+ flow: [0, 0.4],
345
+ perch: [0, 0.45],
346
+ room: 1.2,
347
+ size: [0.5, 0.9],
348
+ sway: 0.05,
349
+ weight: 3,
350
+ width: 24,
351
+ },
352
+ /** One line into the flow, and the thing on this reef that moves most. */
353
+ whip: {
354
+ cling: 0.25,
355
+ flow: [0.5, 1],
356
+ perch: [0.4, 1],
357
+ room: 0.9,
358
+ size: [0.7, 1.4],
359
+ sway: 0.22,
360
+ weight: 3,
361
+ width: 12,
362
+ },
363
+ }
364
+
365
+ /** Every kind there is, which is what `Reef.health` is measured against. */
366
+ export const KINDS = Object.keys(SORTS) as readonly HeadKind[]
367
+
368
+ /**
369
+ * What each kind is, as strokes from one base.
370
+ *
371
+ * `flora.ts` argues for the idiom and it holds here: round caps on tapering
372
+ * strokes read as a growing thing without a single closed outline being drawn,
373
+ * and a kind is told apart from its neighbour by how wide it gets against how
374
+ * tall and how thick the strokes are. Nose-up, base at the origin, in the same
375
+ * units the bed's corals are drawn in, so one renderer serves both.
376
+ */
377
+ export const SHAPES: Readonly<Record<HeadKind, readonly Twig[]>> = {
378
+ /** None. It is the one animal on the rock, and an animal is not a stamp. */
379
+ anemone: [],
380
+ brain: [
381
+ { d: "M-13 0 C-13 -8 -8 -13 -1 -14", width: 7 },
382
+ { d: "M-6 0 C-7 -7 -4 -11 0 -12", width: 7 },
383
+ { d: "M6 0 C7 -7 5 -11 1 -13", width: 7 },
384
+ { d: "M13 -1 C13 -8 8 -13 1 -14", width: 7 },
385
+ ],
386
+ elkhorn: [
387
+ { d: "M0 0 C0 -5 -1 -9 -2 -12", width: 7 },
388
+ { d: "M0 0 C-1 -10 -6 -16 -14 -24", width: 6 },
389
+ { d: "M0 0 C1 -12 4 -20 10 -30", width: 5.5 },
390
+ { d: "M-8 -14 C-14 -18 -20 -20 -26 -22", width: 5 },
391
+ { d: "M5 -16 C11 -21 17 -23 24 -25", width: 4.5 },
392
+ ],
393
+ fan: [
394
+ { d: "M0 0 C0 -8 -2 -12 -3 -16", width: 3.5 },
395
+ { d: "M-3 -16 C-9 -20 -13 -24 -15 -30", width: 2 },
396
+ { d: "M-3 -16 C-6 -22 -7 -27 -8 -32", width: 2 },
397
+ { d: "M-3 -16 C0 -22 1 -27 1 -33", width: 2 },
398
+ { d: "M-3 -16 C3 -21 7 -25 10 -30", width: 2 },
399
+ { d: "M-3 -16 C6 -19 11 -21 15 -25", width: 2 },
400
+ ],
401
+ leather: [
402
+ { d: "M0 0 C-2 -6 -6 -9 -9 -15", width: 8 },
403
+ { d: "M0 0 C0 -7 1 -12 2 -18", width: 8 },
404
+ { d: "M0 0 C3 -6 7 -9 10 -14", width: 7 },
405
+ ],
406
+ plate: [
407
+ { d: "M0 0 C0 -2 0 -3 0 -4", width: 6 },
408
+ { d: "M0 0 C-6 -2 -14 -4 -22 -5", width: 5 },
409
+ { d: "M0 0 C6 -2 14 -4 21 -6", width: 5 },
410
+ { d: "M-14 -4 C-15 -7 -14 -8 -12 -10", width: 3 },
411
+ { d: "M15 -5 C17 -7 17 -9 16 -11", width: 3 },
412
+ ],
413
+ staghorn: [
414
+ { d: "M0 0 C-1 -11 -3 -20 -7 -31", width: 4 },
415
+ { d: "M0 0 C2 -9 5 -17 7 -28", width: 3.5 },
416
+ { d: "M-4 -19 C-9 -24 -13 -26 -18 -33", width: 2.4 },
417
+ { d: "M5 -18 C9 -24 13 -25 17 -30", width: 2.4 },
418
+ { d: "M-2 -8 C-7 -11 -11 -12 -15 -13", width: 2.2 },
419
+ { d: "M3 -10 C8 -13 11 -15 15 -17", width: 2.2 },
420
+ ],
421
+ table: [
422
+ { d: "M0 0 C-1 -6 -1 -10 0 -13", width: 6 },
423
+ { d: "M0 -13 C-8 -14 -16 -15 -23 -15", width: 4 },
424
+ { d: "M0 -13 C8 -14 16 -15 23 -16", width: 4 },
425
+ { d: "M-12 -15 C-13 -17 -13 -19 -12 -20", width: 2.4 },
426
+ { d: "M11 -15 C12 -17 12 -19 11 -21", width: 2.4 },
427
+ ],
428
+ tube: [
429
+ { d: "M-8 0 C-9 -8 -9 -14 -8 -19", width: 6 },
430
+ { d: "M0 0 C-1 -9 -1 -18 0 -25", width: 6.5 },
431
+ { d: "M8 -1 C9 -7 9 -12 8 -16", width: 5.5 },
432
+ ],
433
+ whip: [
434
+ { d: "M0 0 C-2 -9 -4 -16 -4 -24", width: 2.2 },
435
+ { d: "M0 0 C1 -12 -1 -24 2 -38", width: 2.6 },
436
+ ],
437
+ }
438
+
439
+ /** Where the mound sits across the box, and how much of it it may take. */
440
+ const SEAT_LEAST = 0.26
441
+
442
+ const SEAT_SPAN = 0.48
443
+
444
+ const HALF_LEAST = 0.15
445
+
446
+ const HALF_SPAN = 0.12
447
+
448
+ /** How high it stands off the ground, as a share of the box. */
449
+ const RISE_LEAST = 0.1
450
+
451
+ const RISE_SPAN = 0.08
452
+
453
+ /**
454
+ * How tall the mound may stand against its own width.
455
+ *
456
+ * A reef is a mound and not a tower. On a tall narrow window the height it
457
+ * would take from the box is more than the width there is to put it on, and
458
+ * what that builds is a spire with a coral balanced on the top of it.
459
+ */
460
+ const TALLEST = 0.62
461
+
462
+ /** How far back it stands. Never up against the glass; it is scenery. */
463
+ const DEPTH_FAR = 0.5
464
+
465
+ const DEPTH_NEAR = 0.82
466
+
467
+ /**
468
+ * The mound's profile: how sharply its sides fall, and how flat its top is.
469
+ *
470
+ * A reef is not a hill. What it does is stand up out of the ground at a steep
471
+ * face and then run nearly level along the top, because the top is where it has
472
+ * grown up to and stopped, and a dome would say the opposite: that the middle
473
+ * grew fastest, which is what a pile of sand does and not what a reef does.
474
+ */
475
+ const BROW = 2.6
476
+
477
+ const BULGE = 0.5
478
+
479
+ /** How much taller one end is than the other, so it has a windward side. */
480
+ const LIST = 0.22
481
+
482
+ /** How many lumps fit across the mound, and how much of its rise they take. */
483
+ const LUMP_CELLS = 3.4
484
+
485
+ const LUMP_SHARE = 0.24
486
+
487
+ /** How many points the skyline is cut into. */
488
+ const CREST_STEPS = 96
489
+
490
+ /** How many corals to grow once every kind has had its one. */
491
+ const HEADS = 52
492
+
493
+ /**
494
+ * How far past the rock the reef's own rubble reaches, against its half-width.
495
+ *
496
+ * A reef does not stop where the rock stops. What surrounds one is an apron of
497
+ * its own broken pieces, and the things that want shelter and shade live out
498
+ * there rather than on the mound: it is the only ground on the whole reef that
499
+ * is flat, still and low. Without it every soft coral competes for the foot of
500
+ * the face and most of them lose, which is a reef with a bare skirt around it.
501
+ */
502
+ const APRON = 1.28
503
+
504
+ /**
505
+ * How many places a coral will try before it gives up and does not grow.
506
+ *
507
+ * It gives up rather than settling, which is the whole of why the reef comes
508
+ * out banded. A whip that took the first spot it was offered would end up at
509
+ * the foot as often as at the crest, and a reef where everything is anywhere is
510
+ * a reef with no bands in it, which is to say a heap.
511
+ */
512
+ const TRIES = 40
513
+
514
+ /** How far the reef's own water reaches past the rock, against its half-width. */
515
+ const REACH = 0.34
516
+
517
+ /** How far apart in height counts as a shelf of its own, against a width. */
518
+ const TIER = 0.55
519
+
520
+ /** How far above the crest still counts as being over the reef. */
521
+ const HEADROOM = 0.9
522
+
523
+ /** How far a coral may have grown off upright, in radians. */
524
+ const LEAN = 0.22
525
+
526
+ /**
527
+ * How big a coral grows against the mound it is on, in scale per px of rise.
528
+ *
529
+ * Against the mound rather than in px, because the mound is the only thing on
530
+ * screen a coral can be judged against. Sized absolutely, the same coral is a
531
+ * boulder on a narrow window and a pebble on a wide one, and the reef stops
532
+ * being a reef at either end: a handful of shapes filling it, or a bald rock
533
+ * with a rash on it.
534
+ */
535
+ const GROWTH = 0.0072
536
+
537
+ /**
538
+ * How tall an anemone's column stands, in the units the drawings are cut in.
539
+ *
540
+ * Short and stout, which is what `flora.ts` argues for at greater length: what
541
+ * an anemone has instead of height is a crown, and a tall one is a plant.
542
+ */
543
+ const COLUMN = 16
544
+
545
+ /** How thick a crown is stroked, as a share of its column. `flora.ts` again. */
546
+ const GIRTH = 0.15
547
+
548
+ /** How fast a crown works through its own feeling. */
549
+ const CROWN_RATE = 1.1
550
+
551
+ /** How fast the swell crosses the reef, and how many of it fit across. */
552
+ const SWELL_RATE = 0.55
553
+
554
+ const SWELL_CELLS = 1.8
555
+
556
+ /** How much of the lean is the swell passing and how much is the head's own. */
557
+ const SWELL_SHARE = 0.6
558
+
559
+ /** Where an anemone's stalk leaves the rock, and where its crown opens. */
560
+ const ROOT: Point = { x: 0, y: 0 }
561
+
562
+ const MOUTH: Point = { x: 0, y: -COLUMN }
563
+
564
+ /** How far a head moves before it is worth drawing again, in px, by default. */
565
+ const TOLERANCE = 0.25
566
+
567
+ /**
568
+ * How far each kind's drawing reaches from where it is rooted, in its own units.
569
+ *
570
+ * Read off the drawings rather than written down beside them. A shape nudged by
571
+ * eye and a number kept by hand part company on the first edit, and nothing
572
+ * anywhere says so; this cannot go stale because there is only the one copy.
573
+ *
574
+ * Every path in `SHAPES` is an absolute move followed by absolute curves, so
575
+ * the numbers in it are points. A curve stays inside the hull of its own
576
+ * control points, which makes the furthest of them a bound on the whole drawing
577
+ * rather than a sample of it, and a bound is what an estimate of movement wants
578
+ * to be wrong in the safe direction.
579
+ *
580
+ * An anemone has no drawing here, and its reach is asked of the crown it was
581
+ * dealt instead, since a crown is dealt per animal rather than per kind.
582
+ */
583
+ const REACHES = ((): Readonly<Record<HeadKind, number>> => {
584
+ const reaches = {} as Record<HeadKind, number>
585
+ for (const kind of KINDS) reaches[kind] = furthest(SHAPES[kind])
586
+ return reaches
587
+ })()
588
+
589
+ function furthest(twigs: readonly Twig[]): number {
590
+ let worst = 0
591
+
592
+ for (const twig of twigs) {
593
+ const numbers = twig.d.match(/-?\d+(?:\.\d+)?/g) ?? []
594
+ for (let at = 0; at + 1 < numbers.length; at += 2) {
595
+ worst = Math.max(worst, Math.hypot(Number(numbers[at]), Number(numbers[at + 1])))
596
+ }
597
+ }
598
+
599
+ return worst
600
+ }
601
+
602
+ /** A floor for the box, so a sliver of a window never divides by nothing. */
603
+ const MIN_SPAN = 1
604
+
605
+ export function createReef(options: ReefOptions): Reef {
606
+ const noise = makeNoise2(options.seed ^ 0x2b91)
607
+ const gust = makeNoise2(options.seed ^ 0x5ce3)
608
+
609
+ let width = Math.max(MIN_SPAN, options.width)
610
+ let height = Math.max(MIN_SPAN, options.height)
611
+ let floor = options.floor
612
+ let clock = 0
613
+
614
+ let middle = width / 2
615
+ let half = width * HALF_LEAST
616
+ let rise = height * RISE_LEAST
617
+ let depth = DEPTH_FAR
618
+
619
+ const heads: Head[] = []
620
+
621
+ /**
622
+ * The tentacles each head was dealt, or nothing for anything that is a
623
+ * stencil.
624
+ *
625
+ * Beside the heads rather than on them, the way `flora.ts` keeps a plant's
626
+ * sway off the `Plant`. What a renderer wants is the crown cut at this
627
+ * moment, which is `Head.blades`; how it was dealt is this file's business
628
+ * and stays here.
629
+ */
630
+ const crowns: (readonly Tentacle[] | null)[] = []
631
+
632
+ const tolerance = Math.max(0, options.tolerance ?? TOLERANCE)
633
+
634
+ /**
635
+ * How far each head's drawing reaches from where it is rooted, in px.
636
+ *
637
+ * Its kind's reach at its own scale, except for an anemone, whose drawing is
638
+ * the column it stands on and the longest tentacle it happened to be dealt.
639
+ */
640
+ const reaches: number[] = []
641
+
642
+ /**
643
+ * A head as it stood when it was last drawn.
644
+ *
645
+ * The two numbers a drawing is made from and nothing else. Against the last
646
+ * drawing rather than the last step, because a swell that pushed a whip over
647
+ * and let it back has not moved it, and a sum of steps says it has.
648
+ */
649
+ const held: ({ readonly bend: number; readonly phase: number } | null)[] = []
650
+
651
+ let bounds: readonly [number, number] = [0, 0]
652
+ let health = 0
653
+
654
+ /**
655
+ * How high the rock stands at a place along it, from nothing to all of it.
656
+ *
657
+ * The one function everything about this reef is measured in. The crest is
658
+ * cut from it, a coral's `perch` is read off it, and a fish's ceiling is it
659
+ * again, so the rock cannot disagree with itself about where its own top is.
660
+ */
661
+ function climb(t: number): number {
662
+ if (Math.abs(t) >= 1) return 0
663
+ const arch = (1 - Math.abs(t) ** BROW) ** BULGE
664
+ const lump = 1 + noise(t * LUMP_CELLS, 0) * LUMP_SHARE
665
+ return Math.min(1, Math.max(0, arch * (1 + LIST * t) * lump))
666
+ }
667
+
668
+ /**
669
+ * How hard the water runs at a place along it, still to swept.
670
+ *
671
+ * Height is most of it, since the crest is what meets the water first, and
672
+ * the rest is which way the reef faces: one end takes the weather and the
673
+ * other lies behind the mound in its own shelter.
674
+ */
675
+ function swept(t: number): number {
676
+ return Math.min(1, climb(t) * 0.62 + ((t + 1) / 2) * 0.38)
677
+ }
678
+
679
+ /**
680
+ * How much of the rock is still overhead at a distance, all of it to none.
681
+ *
682
+ * The mound is a body rather than a wall, so its front slopes away from the
683
+ * crest towards the viewer, and something standing a little forward of it is
684
+ * standing on the part of that slope which has already come down. At the
685
+ * glass there is no rock left at all, which is what open water in front of a
686
+ * reef is.
687
+ */
688
+ const standing = (stand: number): number => {
689
+ if (stand <= depth) return 1
690
+ return 1 - Math.min(1, (stand - depth) / Math.max(1 - depth, 1e-6))
691
+ }
692
+
693
+ const surfaceAt = (x: number, stand: number = depth): number => {
694
+ const t = (x - middle) / Math.max(MIN_SPAN, half)
695
+ return floor(x, stand) - rise * climb(t) * standing(stand)
696
+ }
697
+
698
+ /**
699
+ * A coral, if it can find anywhere on this rock it can live.
700
+ *
701
+ * It is handed the reef and nothing else. Where it ends up is its own answer
702
+ * to what it is standing on and who is already standing there, which is why
703
+ * no caller here ever names a place.
704
+ */
705
+ function grow(kind: HeadKind, roll: () => number): Head | null {
706
+ const sort = SORTS[kind]
707
+
708
+ for (let tries = 0; tries < TRIES; tries++) {
709
+ const t = (-1 + roll() * 2) * APRON
710
+ const perch = climb(t)
711
+ const flow = swept(t)
712
+
713
+ if (perch < sort.perch[0] || perch > sort.perch[1]) continue
714
+ if (flow < sort.flow[0] || flow > sort.flow[1]) continue
715
+
716
+ const x = middle + t * half
717
+ const face = sort.cling * perch
718
+ const stand = depth + face * (1 - depth) * roll()
719
+ const grown = sort.size[0] + roll() * (sort.size[1] - sort.size[0])
720
+ const scale = grown * rise * GROWTH * stand
721
+ const span = sort.width * scale
722
+
723
+ const y = surfaceAt(x, stand)
724
+ if (!room(x, y, span * sort.room)) continue
725
+
726
+ const tentacles = kind === "anemone" ? crown(stir(roll() * 0xffffff), COLUMN * scale) : null
727
+
728
+ const head: Head = {
729
+ bend: 0,
730
+ blades: [],
731
+ cut: 0,
732
+ depth: stand,
733
+ girth: tentacles ? COLUMN * GIRTH : 0,
734
+ kind,
735
+ lane: roll() * Math.PI * 2,
736
+ lean: (roll() - 0.5) * LEAN * 2,
737
+ points: tentacles ? [ROOT, MOUTH] : [],
738
+ scale,
739
+ span,
740
+ twigs: gathered(SHAPES[kind]),
741
+ x,
742
+ y,
743
+ }
744
+
745
+ heads.push(head)
746
+ crowns.push(tentacles)
747
+ held.push(null)
748
+ reaches.push(
749
+ tentacles
750
+ ? COLUMN * scale * (1 + Math.max(...tentacles.map((one) => one.span)))
751
+ : REACHES[kind] * scale,
752
+ )
753
+ return head
754
+ }
755
+
756
+ return null
757
+ }
758
+
759
+ /**
760
+ * Whether a coral of that size would be standing on one already there.
761
+ *
762
+ * Height is half the question, and leaving it out was what made the first
763
+ * reef a row. A face has shelves up it, and a fan on one shelf is no argument
764
+ * against a sponge on the shelf below however nearly they share a column, so
765
+ * two corals only crowd each other if they are at much the same height as
766
+ * well as much the same place.
767
+ */
768
+ function room(x: number, y: number, want: number): boolean {
769
+ for (const other of heads) {
770
+ const near = (want + other.span) / 2
771
+ if (Math.abs(other.y - y) > near * TIER) continue
772
+ if (Math.abs(other.x - x) < near) return false
773
+ }
774
+ return true
775
+ }
776
+
777
+ /**
778
+ * The reef this seed builds, from its own generator started afresh.
779
+ *
780
+ * Its own, and restarted whenever the box changes, for the reason `relics.ts`
781
+ * gives: what a sea holds has to be a pure function of its seed, or a window
782
+ * dragged wider deals a second reef and nobody can look at the same one twice.
783
+ */
784
+ function raise(): void {
785
+ const roll = makeRandom(options.seed ^ 0x91af)
786
+
787
+ middle = width * (SEAT_LEAST + roll() * SEAT_SPAN)
788
+ half = width * (HALF_LEAST + roll() * HALF_SPAN)
789
+ rise = Math.min(height * (RISE_LEAST + roll() * RISE_SPAN), half * TALLEST)
790
+ depth = DEPTH_FAR + roll() * (DEPTH_NEAR - DEPTH_FAR)
791
+
792
+ heads.length = 0
793
+ crowns.length = 0
794
+ held.length = 0
795
+ reaches.length = 0
796
+ crest.length = 0
797
+ bounds = [middle - half, middle + half]
798
+
799
+ for (let step = 0; step <= CREST_STEPS; step++) {
800
+ const x = middle - half + ((half * 2) / CREST_STEPS) * step
801
+ crest.push({ x, y: surfaceAt(x) })
802
+ }
803
+
804
+ for (const kind of dealt(roll)) grow(kind, roll)
805
+
806
+ const rest = spread()
807
+ for (let made = 0; made < Math.max(0, options.heads ?? HEADS); made++) {
808
+ grow(rest[Math.floor(roll() * rest.length)] ?? "brain", roll)
809
+ }
810
+
811
+ health = new Set(heads.map((one) => one.kind)).size / KINDS.length
812
+ }
813
+
814
+ /**
815
+ * Every kind, in an order this seed made up.
816
+ *
817
+ * The first pass gives each kind of coral first refusal on a bare rock, and
818
+ * whoever asks first gets the best of the band they share. Asking in the
819
+ * order they happen to be written down means the same kind is last on every
820
+ * reef ever built, and the last one is the one that goes without.
821
+ */
822
+ function dealt(roll: () => number): HeadKind[] {
823
+ const order = [...KINDS]
824
+ for (let at = order.length - 1; at > 0; at--) {
825
+ const swap = Math.floor(roll() * (at + 1))
826
+ const held = order[at] as HeadKind
827
+ order[at] = order[swap] as HeadKind
828
+ order[swap] = held
829
+ }
830
+ return order
831
+ }
832
+
833
+ /** Every kind repeated by its weight, which is the cheapest weighting there is. */
834
+ function spread(): HeadKind[] {
835
+ const bag: HeadKind[] = []
836
+ for (const kind of KINDS) {
837
+ for (let each = 0; each < SORTS[kind].weight; each++) bag.push(kind)
838
+ }
839
+ return bag
840
+ }
841
+
842
+ /** How far a head is leaning at the moment, from the swell and its own phase. */
843
+ function lean(one: Head): number {
844
+ const sway = SORTS[one.kind].sway
845
+ if (sway === 0) return 0
846
+
847
+ const passing = gust(
848
+ ((one.x - middle) / Math.max(MIN_SPAN, half)) * SWELL_CELLS,
849
+ clock * SWELL_RATE,
850
+ )
851
+ const own = Math.sin(clock * Math.PI * 2 * SWELL_RATE + one.lane)
852
+ return sway * (passing * SWELL_SHARE + own * (1 - SWELL_SHARE))
853
+ }
854
+
855
+ /**
856
+ * How far a head can have moved since it was last drawn.
857
+ *
858
+ * Two things move it and both are in here, because one drawing covers the
859
+ * two. The whole of it turns in the swell, and the furthest part of a drawing
860
+ * turned about its root travels its reach for a radian of turn. An anemone's
861
+ * tentacles curl on their own clock besides, whether the water is doing
862
+ * anything or not, and `flora.ts` owns the estimate of that for the reason it
863
+ * owns the crown.
864
+ *
865
+ * Neither is measured off the drawing, because making one to find out is the
866
+ * bill this is here to avoid.
867
+ */
868
+ function wander(at: number, bend: number, phase: number): number {
869
+ const one = heads[at]
870
+ const was = held[at]
871
+ if (!one || !was) return Number.POSITIVE_INFINITY
872
+
873
+ return (
874
+ (reaches[at] ?? 0) * Math.abs(bend - was.bend) +
875
+ crownSwept(COLUMN * one.scale, Math.abs(phase - was.phase))
876
+ )
877
+ }
878
+
879
+ /** Draw again every head the swell has moved far enough to be worth it. */
880
+ function breathe(): void {
881
+ for (const [at, one] of heads.entries()) {
882
+ const tentacles = crowns[at]
883
+ const bend = lean(one)
884
+ const phase = tentacles ? clock * CROWN_RATE + one.lane : 0
885
+
886
+ if (wander(at, bend, phase) < tolerance) continue
887
+
888
+ one.bend = bend
889
+ if (tentacles) one.blades = crownAt(MOUTH, tentacles, COLUMN, phase)
890
+ one.cut++
891
+ held[at] = { bend, phase }
892
+ }
893
+ }
894
+
895
+ const crest: Point[] = []
896
+
897
+ raise()
898
+ breathe()
899
+
900
+ return {
901
+ crest,
902
+
903
+ get depth() {
904
+ return depth
905
+ },
906
+
907
+ heads,
908
+
909
+ get health() {
910
+ return health
911
+ },
912
+
913
+ holds(x, y, stand = depth) {
914
+ const reach = half * (1 + REACH)
915
+ if (x < middle - reach || x > middle + reach) return false
916
+
917
+ const rock = surfaceAt(x, stand)
918
+ return y >= rock - rise * HEADROOM && y <= rock
919
+ },
920
+
921
+ resize(nextWidth, nextHeight, nextFloor) {
922
+ width = Math.max(MIN_SPAN, nextWidth)
923
+ height = Math.max(MIN_SPAN, nextHeight)
924
+ floor = nextFloor
925
+ raise()
926
+ breathe()
927
+ },
928
+
929
+ get span() {
930
+ return bounds
931
+ },
932
+
933
+ step(seconds) {
934
+ clock += Math.min(Math.max(seconds, 0), 0.1)
935
+ breathe()
936
+ },
937
+
938
+ surfaceAt,
939
+
940
+ wind(seconds) {
941
+ clock += Math.max(0, seconds)
942
+ breathe()
943
+ },
944
+ }
945
+ }