@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/LICENSE +661 -0
- package/README.md +62 -2
- package/package.json +35 -3
- package/src/biome.ts +157 -0
- package/src/canvas/flat.ts +336 -0
- package/src/caustics.ts +81 -0
- package/src/census.ts +262 -0
- package/src/cephalopods.ts +1041 -0
- package/src/cloud.ts +308 -0
- package/src/crags.ts +974 -0
- package/src/drift.ts +435 -0
- package/src/eels.ts +667 -0
- package/src/fish_shape.ts +410 -0
- package/src/flora.ts +1955 -0
- package/src/flow_field.ts +491 -0
- package/src/jellies.ts +1794 -0
- package/src/nemos.ts +606 -0
- package/src/paint/bed.ts +231 -0
- package/src/paint/colour.ts +86 -0
- package/src/paint/frame.ts +204 -0
- package/src/paint/ink.ts +134 -0
- package/src/paint/life.ts +611 -0
- package/src/paint/light.ts +132 -0
- package/src/paint/pen.ts +225 -0
- package/src/paint/rock.ts +325 -0
- package/src/paint/sea.ts +809 -0
- package/src/paint/shade.ts +109 -0
- package/src/paint/sky.ts +377 -0
- package/src/paint/specimen.ts +300 -0
- package/src/paint/svg.ts +326 -0
- package/src/paint/trace.ts +164 -0
- package/src/passers.ts +730 -0
- package/src/perlin.ts +168 -0
- package/src/plenty.ts +65 -0
- package/src/puffers.ts +510 -0
- package/src/rays.ts +225 -0
- package/src/reef.ts +945 -0
- package/src/relics.ts +451 -0
- package/src/seabed.ts +525 -0
- package/src/shoal.ts +1658 -0
- package/src/sizes.ts +86 -0
- package/src/sun.ts +500 -0
- package/src/swarm.ts +587 -0
- package/src/visitors.ts +1936 -0
- package/src/walkers.ts +861 -0
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
|
+
}
|