@codincod/ornament 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/eels.ts ADDED
@@ -0,0 +1,667 @@
1
+ /**
2
+ * A colony of garden eels, standing out of the sand.
3
+ *
4
+ * Every other animal in this water is somewhere because of what it decided. A
5
+ * fish went there, a crab crossed to it, a jellyfish was carried. These are the
6
+ * one family that has an address and never leaves it: an eel digs a burrow,
7
+ * lines it, and spends its life with its tail in the hole and the rest of it
8
+ * standing in the current picking at what the water brings past. It is the
9
+ * whole animal, and it is a behaviour nothing else here has.
10
+ *
11
+ * ## Why it is not `flora.ts` with a head on it
12
+ *
13
+ * Because a plant leans and an eel stands. A blade of grass is a thing the
14
+ * water is doing; the eel is holding a posture against the water, and the
15
+ * difference shows in what happens when the current turns. A plant follows it
16
+ * round and takes as long to get there as the water does. An eel is already
17
+ * facing the new way, because facing into it is the point: what is being eaten
18
+ * is arriving on it.
19
+ *
20
+ * So they read the same current the bed reads, and they read it as a direction
21
+ * to face rather than as a force to give in to. What is left of the plant in
22
+ * them is a small bend, which is a body in moving water and not a body being
23
+ * moved by it.
24
+ *
25
+ * ## The wave is the animal
26
+ *
27
+ * One eel retracting is a detail nobody sees. A field of them going down in
28
+ * order, nearest to the thing first, and coming back up in the same order a few
29
+ * seconds later, is the thing people remember about ever having seen one. It is
30
+ * also the only piece of collective behaviour in this water that is not a flock:
31
+ * the flock (`swarm.ts`) is one object with fish drawn on it, and this is a
32
+ * hundred animals that never coordinate at all. The wave is not organised. Each
33
+ * eel reacts to the same event on its own, and what makes a wave of it is that
34
+ * the event reaches them at different times because they are standing at
35
+ * different distances from it. See `WAVE`.
36
+ *
37
+ * That distinction is worth the file. A colony written as a wave, with a
38
+ * position along the wave per eel, looks the same until something crosses at an
39
+ * angle, or crosses twice, or stops halfway; then it is a stadium doing a
40
+ * Mexican wave and this one is still a hundred frightened animals.
41
+ *
42
+ * ## They come back out slowly, and that is not politeness
43
+ *
44
+ * Going in is free and being out is how it eats, so an eel goes down as fast as
45
+ * it can and comes up as slowly as it dares. `SNAP` and `EMERGE` are two orders
46
+ * of magnitude apart on purpose. The asymmetry is what makes a colony that has
47
+ * been disturbed look disturbed for a while afterwards, which is the honest
48
+ * consequence of one shark and the reason a sea with events in it feels
49
+ * different from a sea with animations in it.
50
+ *
51
+ * ## What a renderer gets
52
+ *
53
+ * `body` is a polyline in px from the mouth of the burrow up, and it is all
54
+ * there is: an eel is a line with a blunt end, drawn round-capped, and the
55
+ * blunt end is the head. Recut only when it has moved further than the
56
+ * tolerance, the same bargain `flora.ts` and `walkers.ts` make, and a colony
57
+ * standing still in slack water is very nearly free.
58
+ */
59
+
60
+ import { abreast, type Presence } from "./biome.ts"
61
+ import { makeNoise2, makeRandom } from "./perlin.ts"
62
+ import type { Point } from "./seabed.ts"
63
+ import { BED_OFF, drawnAt } from "./sizes.ts"
64
+
65
+ export interface Eel {
66
+ /**
67
+ * The body, mouth of the burrow first and head last, in px.
68
+ *
69
+ * Recut when the animal has moved further than the tolerance since the last
70
+ * one, so a colony in slack water hands back the same points for minutes.
71
+ */
72
+ body: Point[]
73
+ /**
74
+ * How many times this eel has redrawn itself.
75
+ *
76
+ * The only thing a renderer has to watch. While it holds, `body` is what it
77
+ * already was and a kept drawing is still true.
78
+ */
79
+ cut: number
80
+ /** 0 is as far off as this water goes, 1 is up against the glass. */
81
+ readonly depth: number
82
+ /**
83
+ * How thick it is against its own length, 1 being the colony's usual.
84
+ *
85
+ * A share rather than a width, because how thick an eel is drawn at this
86
+ * distance is the renderer's question and how thick this one is against its
87
+ * neighbours is the colony's.
88
+ */
89
+ readonly girth: number
90
+ /** How far out it is standing, 0 in the hole and 1 at full stretch. */
91
+ out: number
92
+ /** How tall it stands at full stretch, in px. */
93
+ readonly size: number
94
+ readonly x: number
95
+ /** The sand at its burrow. */
96
+ readonly y: number
97
+ }
98
+
99
+ export interface Eels {
100
+ /**
101
+ * Whether a place is inside the colony's clearing.
102
+ *
103
+ * For `flora.ts`, which will not root in one. A garden eel bed is bare sand
104
+ * with weed around the edge of it, and the reason it is bare is the eels:
105
+ * every burrow is being worked over continuously, and nothing gets to settle.
106
+ * Written as a question the bed asks rather than as a shape the bed is handed,
107
+ * because the colony is the one that knows where it dug.
108
+ */
109
+ bare(x: number, depth: number): boolean
110
+ readonly eels: readonly Eel[]
111
+ /**
112
+ * A box of a new size, and the ground that came with it.
113
+ *
114
+ * The colony is dug again rather than moved. A burrow is a hole in a
115
+ * particular patch of sand, and a patch of sand that is now somewhere else is
116
+ * not that patch.
117
+ */
118
+ resize(width: number, height: number, floor: (x: number, depth?: number) => number): void
119
+ step(seconds: number): void
120
+ }
121
+
122
+ export interface EelsOptions {
123
+ /**
124
+ * What else is in the water, asked for once a step.
125
+ *
126
+ * Left out, nothing is, and the colony stands out all day. Which is what a
127
+ * colony with nothing crossing it does.
128
+ */
129
+ about?: () => readonly Presence[]
130
+ /** How many to dig in. Rounded down, and a colony of none is no colony. */
131
+ count?: number
132
+ /** The ground at x, at a given distance; `seabed.floorAt`. */
133
+ floor: (x: number, depth?: number) => number
134
+ height: number
135
+ seed: number
136
+ /** How far an eel may move from its last drawing before it recuts, in px. */
137
+ tolerance?: number
138
+ width: number
139
+ }
140
+
141
+ /**
142
+ * How long one stands, in metres, and how much of that is out of the hole.
143
+ *
144
+ * A garden eel is most of a metre of animal and stands about half of it up. The
145
+ * length is the whole animal because that is what a reader could look up; what
146
+ * is drawn is the share, and the rest is in the sand.
147
+ */
148
+ const LENGTH = 0.72
149
+
150
+ const STANDING = 0.55
151
+
152
+ /** How much longer or shorter than its neighbours one of them is. */
153
+ const ODD = 0.34
154
+
155
+ /**
156
+ * How much thicker or thinner than its length would say one of them is.
157
+ *
158
+ * Drawn apart from the length rather than with it. Girth that follows height
159
+ * exactly is one animal at several scales, which is what a colony of a hundred
160
+ * cannot afford to be: the eye picks a repeated shape out of a crowd long
161
+ * before it picks a repeated movement, so a long thin one and a short thick one
162
+ * have to both be possible.
163
+ */
164
+ const GIRTH_ODD = 0.28
165
+
166
+ /**
167
+ * How wide a colony is, as a share of the box.
168
+ *
169
+ * A patch and not a field. Garden eels live in dense beds with bare sand either
170
+ * side, and a colony spread across the whole picture is a texture rather than a
171
+ * place: what says colony is that there is an edge to it and you can see both
172
+ * ends of it.
173
+ */
174
+ const PATCH = 0.42
175
+
176
+ /**
177
+ * How far back the colony sits, and how deep a band of sand it takes.
178
+ *
179
+ * Not at the front. These are drawn as a hundred thin lines, and a hundred thin
180
+ * lines across the near sand is a thicket in front of everything else in the
181
+ * picture. Set back, they are a patch of texture on the floor with the reef
182
+ * behind them, which is where a reader has ever seen one.
183
+ */
184
+ const DEPTH_AT = 0.52
185
+
186
+ const DEPTH_BAND = 0.26
187
+
188
+ /** How much of a colony's drawn size is its distance. The floor's own share. */
189
+ const DEPTH_SIZE = 0.55
190
+
191
+ /**
192
+ * How many burrows fit in a square of sand, and the fewest a colony is.
193
+ *
194
+ * A real bed is denser than this by a long way. What is being drawn is a
195
+ * silhouette of a bed at a distance, and past a certain crowding the gaps
196
+ * between the animals close and it stops reading as animals at all.
197
+ */
198
+ const CROWDING = 0.00042
199
+
200
+ const FEWEST = 8
201
+
202
+ const MOST = 140
203
+
204
+ /** How far apart two burrows have to be, as a share of one eel's height. */
205
+ const APART = 0.16
206
+
207
+ /**
208
+ * How far past the outermost burrow the clearing reaches, as a share of the
209
+ * colony's own width, and how far either side of it in distance.
210
+ *
211
+ * Generous, because what is being kept out is a plant with a root at a point
212
+ * and a metre of leaf above it: a kelp planted just outside a bed still leans
213
+ * across the whole of it.
214
+ */
215
+ const CLEARING = 0.18
216
+
217
+ const CLEARING_DEEP = 0.16
218
+
219
+ /** How many features of the current fit across the box. The bed's own. */
220
+ const FIELD_CELLS = 3.6
221
+
222
+ /** How fast the field slides, so a colony never settles into one shape. */
223
+ const DRIFT = 0.06
224
+
225
+ /**
226
+ * How far a head is carried off the line of the burrow, as a share of height.
227
+ *
228
+ * Small. An eel in a current is a slightly leaning line and not a bent one:
229
+ * whatever the water is doing, the animal is holding itself against it, and a
230
+ * body that gave in to the current would be a body that had stopped feeding.
231
+ */
232
+ const LEAN = 0.26
233
+
234
+ /** How much of the lean is the shared current, and how much is its own. */
235
+ const CURRENT_SHARE = 0.72
236
+
237
+ /** How fast one works through its own part of the sway, slowest to fastest. */
238
+ const SWAY_SLOWEST = 0.14
239
+
240
+ const SWAY_FASTEST = 0.5
241
+
242
+ /**
243
+ * How much of an eel's own sway is the quick part of it, and how many times
244
+ * faster that part goes.
245
+ *
246
+ * One sine is a pendulum. A garden eel is not swinging, it is picking food out
247
+ * of the water going past, so the slow lean the current puts on it carries a
248
+ * faster, smaller movement of its own head. The ratio is drawn per animal and
249
+ * is not a whole number on purpose: a quick part that comes round with the slow
250
+ * one is a shape that repeats, and a colony of shapes that repeat is a colony
251
+ * of clockwork whatever rate each of them is set to.
252
+ */
253
+ const PICK = 0.34
254
+
255
+ const PICK_TIMES_LEAST = 2.3
256
+
257
+ const PICK_TIMES_SPAN = 1.3
258
+
259
+ /**
260
+ * How much of its length an eel keeps back between one look and the next, and
261
+ * how long it takes over that.
262
+ *
263
+ * Nothing to do with being frightened, which is `wants` and is sudden. This is
264
+ * the ordinary business of the colony: an eel is not a rod at full height until
265
+ * something scares it, it is up a bit and down a bit all day. A bed where every
266
+ * animal is standing at exactly its own full height is the single thing that
267
+ * makes a hundred of them read as a field of identical stakes, and the fix is
268
+ * not more sway. It is that they are at different heights, and not the same
269
+ * different heights a moment later.
270
+ *
271
+ * Slow enough that `EMERGE` tracks it without being the thing shaping it: this
272
+ * sets the height an eel is easing towards, not how fast it may get there.
273
+ */
274
+ const SHY_LEAST = 0.05
275
+
276
+ const SHY_SPAN = 0.24
277
+
278
+ const BREATH_SLOWEST = 0.035
279
+
280
+ const BREATH_FASTEST = 0.12
281
+
282
+ /** How finely a body is cut. Enough that the bend has no corners in it. */
283
+ const STEPS = 5
284
+
285
+ /**
286
+ * How much bigger than an eel a thing has to be before it is minded, and how
287
+ * far off it is minded from as a share of its own size.
288
+ *
289
+ * `walkers.ts` and `nemos.ts` weigh the water by these, and another animal in
290
+ * the same sea should be frightened of the same things. Something an eel's own
291
+ * size is another eel.
292
+ */
293
+ const MINDS = 1.6
294
+
295
+ const NOTICE = 3.2
296
+
297
+ /** How much of a fright something with no menace about it can still cause. */
298
+ const BULK = 0.25
299
+
300
+ /**
301
+ * How little a thing has to worry a colony before nobody bothers.
302
+ *
303
+ * There is a floor because an eel that ducked at everything would be an eel
304
+ * permanently in its hole, and a colony nobody ever sees is a colony that was
305
+ * not worth drawing.
306
+ */
307
+ const MINDS_AT = 0.12
308
+
309
+ /**
310
+ * How fast the news crosses the sand, in px a second.
311
+ *
312
+ * The one number the wave is. Nothing is sent and nothing is heard: an eel
313
+ * ducks when the thing is close enough to it, and the ones further along the
314
+ * bed are further from it, so they duck later. This is what that delay comes
315
+ * out as once it is written as a speed, and writing it as a speed is what lets
316
+ * the wave be right when something crosses at an angle rather than along the
317
+ * row.
318
+ *
319
+ * Fast enough to look like one event and slow enough to be a wave. Much faster
320
+ * and the whole colony drops at once, which is a colony with a switch on it.
321
+ */
322
+ const WAVE = 620
323
+
324
+ /**
325
+ * How long one stays down, in seconds, and how much longer a real fright keeps
326
+ * it there.
327
+ *
328
+ * The floor is short because most of what crosses a bed is nothing, and an eel
329
+ * that took a turtle as seriously as a shark would spend its life underground.
330
+ */
331
+ const HOLD_LEAST = 1.4
332
+
333
+ const HOLD_SPAN = 5.5
334
+
335
+ /** How much of its own time one eel keeps, so a colony never comes up as a row. */
336
+ const DITHER = 1.6
337
+
338
+ /**
339
+ * How fast it goes down and how fast it comes back up, in its own heights a
340
+ * second.
341
+ *
342
+ * Two orders apart. Going down is free and being up is how it eats, so it drops
343
+ * as fast as it can and returns as slowly as it dares. Ten seconds after a
344
+ * shark, a bed that snapped back would look like a bed that had not noticed.
345
+ */
346
+ const SNAP = 9
347
+
348
+ const EMERGE = 0.26
349
+
350
+ /**
351
+ * How far down a startled eel actually goes.
352
+ *
353
+ * Not away. A garden eel keeps its head at the mouth of the burrow with
354
+ * everything else in it, because the hole is the safe place and looking at what
355
+ * is in the water is worth something. A colony that vanished entirely would
356
+ * leave bare sand, and bare sand is the wrong picture: what happens is that the
357
+ * grass goes.
358
+ */
359
+ const HIDDEN = 0.08
360
+
361
+ /** How far an eel travels before it is worth redrawing, in px, by default. */
362
+ const TOLERANCE = 0.25
363
+
364
+ const MIN_SPAN = 1
365
+
366
+ /**
367
+ * What frightened the colony, when, and from where.
368
+ *
369
+ * One of these at a time. A bed being crossed by two things at once is rare
370
+ * enough that keeping both would be machinery for a case nobody sees, and the
371
+ * nearer of them is the one that matters anyway.
372
+ */
373
+ interface Alarm {
374
+ at: number
375
+ force: number
376
+ x: number
377
+ y: number
378
+ }
379
+
380
+ /** Everything about one eel that is not in the drawing. */
381
+ interface Habit {
382
+ /** How fast it works through its own part of the sway. */
383
+ readonly beat: number
384
+ /** How fast it rises and dips out of its own burrow, doing nothing else. */
385
+ readonly breath: number
386
+ /** Where in that rise and dip it is. */
387
+ readonly breathed: number
388
+ /** How much of its own time it keeps, so no two come up together. */
389
+ readonly dither: number
390
+ /** Where its head was when it was last drawn, or null before the first. */
391
+ drawn: Point | null
392
+ /** Where in that sway it is, so no two are at the same point of it. */
393
+ readonly own: number
394
+ /** How many times faster the quick part of its sway goes than the slow. */
395
+ readonly picks: number
396
+ /** How much of its length it keeps back at the bottom of its own dip. */
397
+ readonly shy: number
398
+ }
399
+
400
+ export function createEels(options: EelsOptions): Eels {
401
+ const noise = makeNoise2(options.seed ^ 0x2d71)
402
+ const random = makeRandom(options.seed ^ 0x51a4)
403
+
404
+ let width = Math.max(MIN_SPAN, options.width)
405
+ let height = Math.max(MIN_SPAN, options.height)
406
+ let floor = options.floor
407
+ let drift = 0
408
+ let clock = 0
409
+
410
+ const tolerance = Math.max(0, options.tolerance ?? TOLERANCE)
411
+
412
+ const eels: Eel[] = []
413
+ const habits: Habit[] = []
414
+
415
+ let alarm: Alarm | null = null
416
+
417
+ /** The clearing, as it stands. Nothing while nothing has been dug. */
418
+ let clearing: { deep: number; left: number; right: number; shallow: number } | null = null
419
+
420
+ /**
421
+ * Where the colony is dug, this box.
422
+ *
423
+ * One patch, placed off the seed, so the same water puts it in the same place
424
+ * every time it is drawn and a different day puts it somewhere else.
425
+ */
426
+ function dig(): void {
427
+ eels.length = 0
428
+ habits.length = 0
429
+ clearing = null
430
+
431
+ const wanted = options.count ?? width * height * CROWDING
432
+ const many = Math.min(MOST, Math.floor(Math.max(0, wanted)))
433
+ if (many < FEWEST) return
434
+
435
+ const patch = width * PATCH
436
+ const middle = patch / 2 + random() * (width - patch)
437
+
438
+ // Placed by rejection rather than by a grid, and then sorted, because a
439
+ // colony is a patch of holes and a grid of holes is a graveyard. What the
440
+ // sorting is for is the drawing: an eel behind another has to be drawn
441
+ // first, and depth is the only thing that settles that.
442
+ const dug: { depth: number; x: number }[] = []
443
+ for (let tries = 0; tries < many * 12 && dug.length < many; tries++) {
444
+ const depth = DEPTH_AT + (random() - 0.5) * DEPTH_BAND
445
+ const x = middle + (random() - 0.5) * patch
446
+ const size = stands(depth)
447
+
448
+ let clear = true
449
+ for (const other of dug) {
450
+ if (Math.abs(other.x - x) < size * APART && Math.abs(other.depth - depth) < APART * 0.2) {
451
+ clear = false
452
+ break
453
+ }
454
+ }
455
+ if (clear) dug.push({ depth, x })
456
+ }
457
+
458
+ dug.sort((one, two) => one.depth - two.depth)
459
+
460
+ for (const hole of dug) {
461
+ const size = stands(hole.depth) * (1 + (random() - 0.5) * 2 * ODD)
462
+ eels.push({
463
+ body: [],
464
+ cut: 0,
465
+ depth: hole.depth,
466
+ girth: 1 + (random() - 0.5) * 2 * GIRTH_ODD,
467
+ // Standing already, because a colony that rose out of the sand on the
468
+ // first frame would be a colony announcing itself.
469
+ out: 1,
470
+ size,
471
+ x: hole.x,
472
+ y: floor(hole.x, hole.depth),
473
+ })
474
+ habits.push({
475
+ beat: SWAY_SLOWEST + random() * (SWAY_FASTEST - SWAY_SLOWEST),
476
+ breath: BREATH_SLOWEST + random() * (BREATH_FASTEST - BREATH_SLOWEST),
477
+ breathed: random() * Math.PI * 2,
478
+ dither: random() * DITHER,
479
+ drawn: null,
480
+ own: random() * Math.PI * 2,
481
+ picks: PICK_TIMES_LEAST + random() * PICK_TIMES_SPAN,
482
+ shy: SHY_LEAST + random() * SHY_SPAN,
483
+ })
484
+ }
485
+
486
+ let left = Number.POSITIVE_INFINITY
487
+ let right = Number.NEGATIVE_INFINITY
488
+ let shallow = Number.POSITIVE_INFINITY
489
+ let deep = Number.NEGATIVE_INFINITY
490
+ for (const one of eels) {
491
+ left = Math.min(left, one.x)
492
+ right = Math.max(right, one.x)
493
+ shallow = Math.min(shallow, one.depth)
494
+ deep = Math.max(deep, one.depth)
495
+ }
496
+
497
+ const edge = (right - left) * CLEARING
498
+ clearing = {
499
+ deep: deep + CLEARING_DEEP,
500
+ left: left - edge,
501
+ right: right + edge,
502
+ shallow: shallow - CLEARING_DEEP,
503
+ }
504
+ }
505
+
506
+ /** How tall one at this distance stands, in px. */
507
+ function stands(depth: number): number {
508
+ const drawn = drawnAt(LENGTH, BED_OFF) * STANDING * width
509
+ return drawn * (1 - DEPTH_SIZE + DEPTH_SIZE * depth)
510
+ }
511
+
512
+ /**
513
+ * How much the water in front of the colony worries it, and where from.
514
+ *
515
+ * Read once for the whole bed rather than once an eel. Every eel in a colony
516
+ * is looking at the same stretch of water, and what tells them apart is where
517
+ * they are standing in it, which is the wave's business rather than this.
518
+ */
519
+ function minded(water: readonly Presence[]): Alarm | null {
520
+ const one = eels[0]
521
+ if (!one) return null
522
+
523
+ let worst: Alarm | null = null
524
+
525
+ for (const thing of water) {
526
+ if (thing.size < one.size * MINDS) continue
527
+
528
+ const reach = thing.size * NOTICE
529
+ let near = 0
530
+ for (const eel of eels) {
531
+ const away = Math.hypot(eel.x - thing.x, eel.y - thing.y)
532
+ if (away >= reach) continue
533
+ near = Math.max(near, (1 - away / reach) * abreast(eel.depth, thing.depth))
534
+ }
535
+ if (near <= 0) continue
536
+
537
+ const force = near * (BULK + (thing.menace ?? 0) * (1 - BULK))
538
+ if (force < MINDS_AT || (worst && force <= worst.force)) continue
539
+
540
+ worst = { at: clock, force, x: thing.x, y: thing.y }
541
+ }
542
+
543
+ return worst
544
+ }
545
+
546
+ /** Where the current is going at a place, as a lean off upright. */
547
+ function current(x: number, y: number): number {
548
+ return noise((x / width) * FIELD_CELLS + drift, (y / height) * FIELD_CELLS)
549
+ }
550
+
551
+ /** How far out this eel wants to be standing, right now. */
552
+ function wants(at: number): number {
553
+ const one = eels[at]
554
+ const habit = habits[at]
555
+ if (!one || !habit) return 1
556
+
557
+ // What it is doing when nothing is happening, which is most of the time and
558
+ // is not nothing: up a bit and down a bit, on its own clock.
559
+ const easy =
560
+ 1 - habit.shy * (0.5 + 0.5 * Math.sin(clock * habit.breath * Math.PI * 2 + habit.breathed))
561
+ if (!alarm) return easy
562
+
563
+ // The wave, and the whole of it. The news has not arrived until the thing
564
+ // was near enough for long enough, where near enough is a distance and long
565
+ // enough is that distance divided by a speed.
566
+ const reach = Math.hypot(one.x - alarm.x, one.y - alarm.y)
567
+ const due = alarm.at + reach / WAVE
568
+ if (clock < due) return easy
569
+
570
+ const held = HOLD_LEAST + HOLD_SPAN * alarm.force + habit.dither
571
+ if (clock >= due + held) return easy
572
+
573
+ // Frightened is a floor rather than a share of what it happened to be
574
+ // doing: an eel that ducks only as far as its own dip had left it is an eel
575
+ // that ducks less the more it was already down.
576
+ return Math.min(easy, 1 - alarm.force * (1 - HIDDEN))
577
+ }
578
+
579
+ /** The body as a polyline, from the mouth of the burrow to the head. */
580
+ function cut(at: number): Point[] {
581
+ const one = eels[at]
582
+ const habit = habits[at]
583
+ if (!one || !habit) return []
584
+
585
+ const up = one.size * one.out
586
+ const shared = current(one.x, one.y)
587
+ const slow = Math.sin(clock * habit.beat * Math.PI * 2 + habit.own)
588
+ const quick = Math.sin(clock * habit.beat * habit.picks * Math.PI * 2 + habit.own * habit.picks)
589
+ const own = slow * (1 - PICK) + quick * PICK
590
+ const lean = (shared * CURRENT_SHARE + own * (1 - CURRENT_SHARE)) * LEAN * up
591
+
592
+ const points: Point[] = []
593
+ for (let step = 0; step <= STEPS; step++) {
594
+ const along = step / STEPS
595
+ // Squared, so the burrow end is upright and the bend is all near the
596
+ // head. An eel is held rigid where it comes out of the sand, because that
597
+ // is the part of it that is gripping.
598
+ points.push({ x: one.x + lean * along * along, y: one.y - up * along })
599
+ }
600
+ return points
601
+ }
602
+
603
+ dig()
604
+
605
+ return {
606
+ bare(x: number, depth: number): boolean {
607
+ if (!clearing) return false
608
+ return (
609
+ x >= clearing.left &&
610
+ x <= clearing.right &&
611
+ depth >= clearing.shallow &&
612
+ depth <= clearing.deep
613
+ )
614
+ },
615
+
616
+ eels,
617
+
618
+ resize(next: number, tall: number, ground: (x: number, depth?: number) => number): void {
619
+ width = Math.max(MIN_SPAN, next)
620
+ height = Math.max(MIN_SPAN, tall)
621
+ floor = ground
622
+ dig()
623
+ },
624
+
625
+ step(seconds: number): void {
626
+ if (eels.length === 0) return
627
+
628
+ clock += seconds
629
+ drift += seconds * DRIFT
630
+
631
+ const water = options.about?.() ?? []
632
+ const worse = minded(water)
633
+ // Kept until something worse arrives or the bed has finished with it, so
634
+ // one shark is one event rather than an event on every frame it is still
635
+ // in the water for.
636
+ if (worse && (!alarm || worse.force > alarm.force || clock > alarm.at + HOLD_LEAST)) {
637
+ alarm = worse
638
+ }
639
+
640
+ for (let at = 0; at < eels.length; at++) {
641
+ const one = eels[at]
642
+ const habit = habits[at]
643
+ if (!one || !habit) continue
644
+
645
+ const want = wants(at)
646
+ const rate = (want < one.out ? SNAP : EMERGE) * seconds
647
+ one.out += Math.max(-rate, Math.min(rate, want - one.out))
648
+
649
+ // Against the last drawing rather than the last step, so an eel that
650
+ // swayed and came back has not moved. The head is the fastest part of
651
+ // it and the only part that moves in two directions, so how far the
652
+ // head has gone is how far the animal has gone: standing still in a
653
+ // turning current is movement, and rising in slack water is movement.
654
+ const body = cut(at)
655
+ const head = body.at(-1)
656
+ if (!head) continue
657
+ if (habit.drawn && Math.hypot(head.x - habit.drawn.x, head.y - habit.drawn.y) < tolerance) {
658
+ continue
659
+ }
660
+
661
+ habit.drawn = head
662
+ one.body = body
663
+ one.cut++
664
+ }
665
+ },
666
+ }
667
+ }