@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/paint/sea.ts
ADDED
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A whole sea, from the seed it grew from to the block of numbers a renderer
|
|
3
|
+
* reads.
|
|
4
|
+
*
|
|
5
|
+
* The simulations decide what is in the water and where it is; `pen.ts` says
|
|
6
|
+
* what one drawing looks like; the files beside it turn one kind of thing into
|
|
7
|
+
* drawings. This is what holds all of that together and hands out a frame.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here draws. What comes back from `over` is a Float32Array with every
|
|
10
|
+
* drawing in the frame written into it, and what a renderer does with that is
|
|
11
|
+
* its own business: the site's `render/card.ts` cuts it into triangles for a graphics card,
|
|
12
|
+
* its `render/flat.ts` walks it onto a canvas, and the desktop's own renderer reads the
|
|
13
|
+
* same block out of V8.
|
|
14
|
+
*
|
|
15
|
+
* ## Why it is a factory
|
|
16
|
+
*
|
|
17
|
+
* Because a page has more than one sea on it. A hero has a band of water behind
|
|
18
|
+
* its words, a design system page has a specimen of every animal, and each of
|
|
19
|
+
* them is its own box with its own seed and its own day. A module holding one
|
|
20
|
+
* scene in module state can serve one screen, which is what a wallpaper is and
|
|
21
|
+
* what a page is not.
|
|
22
|
+
*
|
|
23
|
+
* ## Two ways to hand over the bed
|
|
24
|
+
*
|
|
25
|
+
* `bed: "marks"` paints the ground and every plant into the frame with
|
|
26
|
+
* everything else, which is what a renderer without a vertex shader wants.
|
|
27
|
+
* `bed: "wire"` leaves them out of the frame and hands them over instead
|
|
28
|
+
* through `layout` and `publish`: the shape once, and after that two numbers a
|
|
29
|
+
* plant, for a renderer that would rather bend them on the card.
|
|
30
|
+
*/
|
|
31
|
+
import { createBiome } from "../biome.ts"
|
|
32
|
+
import { createCaustics } from "../caustics.ts"
|
|
33
|
+
import { type Kind, manyIn } from "../census.ts"
|
|
34
|
+
import { createCephalopods } from "../cephalopods.ts"
|
|
35
|
+
import { createClouds } from "../cloud.ts"
|
|
36
|
+
import { createCrags } from "../crags.ts"
|
|
37
|
+
import { createDrift } from "../drift.ts"
|
|
38
|
+
import { createEels } from "../eels.ts"
|
|
39
|
+
import { createFlora, type Plant } from "../flora.ts"
|
|
40
|
+
import { createJellies, KINDS as JELLY_KINDS } from "../jellies.ts"
|
|
41
|
+
import { createNemos } from "../nemos.ts"
|
|
42
|
+
import { createPassers } from "../passers.ts"
|
|
43
|
+
import { thriving } from "../plenty.ts"
|
|
44
|
+
import { createPuffers } from "../puffers.ts"
|
|
45
|
+
import { createRays } from "../rays.ts"
|
|
46
|
+
import { createReef } from "../reef.ts"
|
|
47
|
+
import { createRelics } from "../relics.ts"
|
|
48
|
+
import { createSeabed } from "../seabed.ts"
|
|
49
|
+
import { createShoal, daySeed, KINDS as FISH_KINDS, wild } from "../shoal.ts"
|
|
50
|
+
import { createSwarm } from "../swarm.ts"
|
|
51
|
+
import { createVisitors } from "../visitors.ts"
|
|
52
|
+
import { createWalkers } from "../walkers.ts"
|
|
53
|
+
import { paintCaustics, paintFlora, paintGround, paintWater } from "./bed.ts"
|
|
54
|
+
import {
|
|
55
|
+
paintEels,
|
|
56
|
+
paintInklings,
|
|
57
|
+
paintJellies,
|
|
58
|
+
paintNemos,
|
|
59
|
+
paintPassers,
|
|
60
|
+
paintPuffers,
|
|
61
|
+
paintShoal,
|
|
62
|
+
paintSwarm,
|
|
63
|
+
paintVisitors,
|
|
64
|
+
paintWalkers,
|
|
65
|
+
} from "./life.ts"
|
|
66
|
+
import { paintRays, paintSnow } from "./light.ts"
|
|
67
|
+
import { Pen } from "./pen.ts"
|
|
68
|
+
import { paintCrags, paintHeads, paintRelics, paintStones } from "./rock.ts"
|
|
69
|
+
import { type Caster, paintShade } from "./shade.ts"
|
|
70
|
+
import { type Asked, paintSky } from "./sky.ts"
|
|
71
|
+
|
|
72
|
+
/** How thickly the bed is sown, per thousand px of width. */
|
|
73
|
+
const PER_K = {
|
|
74
|
+
anemones: 34,
|
|
75
|
+
cliffs: 5.5,
|
|
76
|
+
corals: 46,
|
|
77
|
+
fans: 19,
|
|
78
|
+
grasses: 124,
|
|
79
|
+
kelps: 24,
|
|
80
|
+
stones: 40,
|
|
81
|
+
} as const
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The most of each the bed will ever hold.
|
|
85
|
+
*
|
|
86
|
+
* High enough that a rich day is drawn rather than clipped, and no higher. What
|
|
87
|
+
* a given renderer can actually afford is its own business and it says so with
|
|
88
|
+
* `thin`, since the ceiling that suits a wallpaper on a desktop card is not the
|
|
89
|
+
* one that suits a band of water behind a paragraph.
|
|
90
|
+
*/
|
|
91
|
+
const MOST = {
|
|
92
|
+
anemones: 200,
|
|
93
|
+
cliffs: 16,
|
|
94
|
+
corals: 500,
|
|
95
|
+
fans: 220,
|
|
96
|
+
grasses: 1400,
|
|
97
|
+
kelps: 280,
|
|
98
|
+
stones: 108,
|
|
99
|
+
} as const
|
|
100
|
+
|
|
101
|
+
const LEAST = 2
|
|
102
|
+
const REEF_HEADS = 34
|
|
103
|
+
const RANGES = 6
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* How far apart two openings of the same water can be, in seconds.
|
|
107
|
+
*
|
|
108
|
+
* Past this the scene is a scene anybody has seen, so there is nothing to be
|
|
109
|
+
* gained by winding further and a wind is not free.
|
|
110
|
+
*/
|
|
111
|
+
const CYCLE = 150
|
|
112
|
+
|
|
113
|
+
/** Sand, a hill, a mound and a cliff, on the wire. */
|
|
114
|
+
const GROUND = { cliff: 2, hill: 1, mound: 3, sand: 0 } as const
|
|
115
|
+
|
|
116
|
+
/** What a plant's kind comes to on the wire. */
|
|
117
|
+
const WIRE_KINDS: Record<string, number> = { anemone: 2, coral: 4, fan: 3, grass: 1, kelp: 0 }
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The floors and ceilings on what the water is allowed to hold.
|
|
121
|
+
*
|
|
122
|
+
* How many of each there are is `census.ts`'s answer, and these are what a
|
|
123
|
+
* frame will actually carry: a floor so a sliver of a box still has a sea in
|
|
124
|
+
* it, and a ceiling so a bloom is a busy water rather than a dropped frame.
|
|
125
|
+
* `night` is the share of a shoal that is still out after dark.
|
|
126
|
+
*/
|
|
127
|
+
const FISH = { least: 6, most: 52, night: 0.55 }
|
|
128
|
+
const MOTES = { least: 40, most: 280, per: 12_500 }
|
|
129
|
+
const CRAWLERS = { mostCrabs: 14, mostStarfish: 14 }
|
|
130
|
+
const COLONY = { fewest: 10, most: 120 }
|
|
131
|
+
const PUFFERS = { most: 3 }
|
|
132
|
+
const JELLIES = { most: 20 }
|
|
133
|
+
const SHAFTS = 5
|
|
134
|
+
const CLOUDS = 4
|
|
135
|
+
const VENTS = 3
|
|
136
|
+
const SPECKS = 3000
|
|
137
|
+
const MOST_INKLINGS = 4
|
|
138
|
+
|
|
139
|
+
/** How much of its own length a fish swims in a second. */
|
|
140
|
+
const CRUISE = 0.8
|
|
141
|
+
|
|
142
|
+
/** How far the water is wound on before anybody sees it, in steps of a tenth. */
|
|
143
|
+
const WIND_STEP = 1 / 10
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* How much the light has to move before the shoal is told a new number.
|
|
147
|
+
*
|
|
148
|
+
* Dawn takes minutes and the count is worked towards over them, so this is only
|
|
149
|
+
* how often the question is asked.
|
|
150
|
+
*/
|
|
151
|
+
const DAWN_STEP = 0.01
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* How far the frame wanders, as a share of the box's short side, and how far it
|
|
155
|
+
* rolls, in radians.
|
|
156
|
+
*
|
|
157
|
+
* Nobody holds a camera still. A picture that does not move at all is a picture
|
|
158
|
+
* on a tripod, and a tripod is the thing this scene is trying not to be.
|
|
159
|
+
*/
|
|
160
|
+
const SWAY_REACH = 0.005
|
|
161
|
+
const SWAY_ROLL = 0.0022
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* How many numbers one frame of this water is given room for.
|
|
165
|
+
*
|
|
166
|
+
* A busy sea is a few thousand drawings and a drawing is a few dozen numbers,
|
|
167
|
+
* so this is several times over what one has ever come to. It is a float array
|
|
168
|
+
* allocated once that lives as long as the sea does, which is why the default
|
|
169
|
+
* is a page's answer rather than a wallpaper's: a screen wants the room, and a
|
|
170
|
+
* page with a dozen small seas on it wants the megabytes back.
|
|
171
|
+
*/
|
|
172
|
+
const ROOM = 1 << 20
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* A coral's drawing, as numbers.
|
|
176
|
+
*
|
|
177
|
+
* The corals are the one thing in the bed that arrives as SVG, and they are
|
|
178
|
+
* also the one thing that never moves, so each drawing is read once and handed
|
|
179
|
+
* over as coordinates from then on. Every twig in `flora.ts` is a move and one
|
|
180
|
+
* curve, which is the whole of the grammar this needs.
|
|
181
|
+
*/
|
|
182
|
+
const carved = new Map<string, number[]>()
|
|
183
|
+
|
|
184
|
+
function twigOf(d: string): number[] {
|
|
185
|
+
const held = carved.get(d)
|
|
186
|
+
if (held) return held
|
|
187
|
+
|
|
188
|
+
const numbers = d.match(/-?\d+(\.\d+)?/g)
|
|
189
|
+
const cut = numbers ? numbers.map(Number) : []
|
|
190
|
+
carved.set(d, cut)
|
|
191
|
+
return cut
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface SeaOptions {
|
|
195
|
+
/**
|
|
196
|
+
* Where the ground and the plants are handed over.
|
|
197
|
+
*
|
|
198
|
+
* `marks` puts them in the frame with everything else. `wire` leaves them out
|
|
199
|
+
* of it and answers `layout` and `publish` instead.
|
|
200
|
+
*/
|
|
201
|
+
bed?: "marks" | "wire"
|
|
202
|
+
/**
|
|
203
|
+
* Wind the rare things on with everything else.
|
|
204
|
+
*
|
|
205
|
+
* A passer keeps an appointment rather than a stopwatch and a shark is out
|
|
206
|
+
* once in an evening, so a still taken the way a scene opens one is a still of
|
|
207
|
+
* empty water. A harness comparing two renderers needs a sea with something
|
|
208
|
+
* in it; a page does not, and this is off for one.
|
|
209
|
+
*/
|
|
210
|
+
eager?: boolean
|
|
211
|
+
height: number
|
|
212
|
+
/**
|
|
213
|
+
* An hour asked for rather than read off the clock; see `sky.ts`.
|
|
214
|
+
*/
|
|
215
|
+
hour?: Asked | null
|
|
216
|
+
/**
|
|
217
|
+
* A block to write frames into, rather than one of this sea's own.
|
|
218
|
+
*
|
|
219
|
+
* For a host that has to know where the block is before there is a sea to
|
|
220
|
+
* hold it. The desktop's renderer reads the address out of V8 once, as the
|
|
221
|
+
* bundle finishes loading, and borrows it from then on; a sea that allocated
|
|
222
|
+
* its own would be writing somewhere the host is not reading. `room` is
|
|
223
|
+
* ignored when this is given, since the block brought is the room there is.
|
|
224
|
+
*/
|
|
225
|
+
into?: Float32Array
|
|
226
|
+
/**
|
|
227
|
+
* How far a thing may move from its last drawing before it is drawn again, in
|
|
228
|
+
* px.
|
|
229
|
+
*/
|
|
230
|
+
/**
|
|
231
|
+
* How many numbers a frame of this water is given room for.
|
|
232
|
+
*
|
|
233
|
+
* Raise it for a whole screen. A frame that runs out ends on a whole drawing
|
|
234
|
+
* rather than halfway through one, so what a block too small costs is the
|
|
235
|
+
* last few things in the picture and never a picture that cannot be read.
|
|
236
|
+
*/
|
|
237
|
+
room?: number
|
|
238
|
+
/**
|
|
239
|
+
* Which water this is. The calendar day by default, which is what makes
|
|
240
|
+
* tomorrow somewhere else.
|
|
241
|
+
*/
|
|
242
|
+
seed?: number
|
|
243
|
+
/**
|
|
244
|
+
* How much of the bed this renderer will carry, as a share of the ceilings
|
|
245
|
+
* above.
|
|
246
|
+
*
|
|
247
|
+
* One number rather than a table, because what varies between renderers is
|
|
248
|
+
* how much of a frame they have and not which plant they like. At 1 the bed
|
|
249
|
+
* is as thick as a good day makes it.
|
|
250
|
+
*/
|
|
251
|
+
thin?: number
|
|
252
|
+
tolerance?: number
|
|
253
|
+
width: number
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export interface Sea {
|
|
257
|
+
/** How far the box is across, as the sea last understood it. */
|
|
258
|
+
readonly box: { height: number; width: number }
|
|
259
|
+
/** The block every frame is written into. Read it, do not keep it. */
|
|
260
|
+
readonly geometry: Float32Array
|
|
261
|
+
/** The bed, once, for a renderer bending it on the card. */
|
|
262
|
+
layout(): number
|
|
263
|
+
/** Everything that moves, this frame. Returns how many numbers were written. */
|
|
264
|
+
over(): number
|
|
265
|
+
/** Where every plant's sway has got to, for a renderer bending it on the card. */
|
|
266
|
+
publish(): number
|
|
267
|
+
resize(width: number, height: number): void
|
|
268
|
+
/** The seed this water grew from. */
|
|
269
|
+
readonly seed: number
|
|
270
|
+
step(seconds: number): void
|
|
271
|
+
/** Carry the water on by a stretch of time, as cheaply as it can be done. */
|
|
272
|
+
wind(seconds: number): void
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function spread(perThousand: number, most: number, width: number, thin: number): number {
|
|
276
|
+
return Math.max(
|
|
277
|
+
LEAST,
|
|
278
|
+
Math.min(Math.round(most * thin), Math.round((width * perThousand) / 1000)),
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** The same, for anything that grows, which is as thick as the day decided. */
|
|
283
|
+
function lush(perThousand: number, most: number, width: number, day: number, thin: number): number {
|
|
284
|
+
return spread(perThousand * day * thin, most, width, thin)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Which sea today is.
|
|
289
|
+
*
|
|
290
|
+
* The local calendar day, stirred. A day is the unit because both of the
|
|
291
|
+
* obvious ones are wrong: one fixed seed is the same sea forever, and a seed off
|
|
292
|
+
* the clock is a new seabed on every visit.
|
|
293
|
+
*/
|
|
294
|
+
export function today(): number {
|
|
295
|
+
return daySeed()
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function createSea(options: SeaOptions): Sea {
|
|
299
|
+
const wired = options.bed === "wire"
|
|
300
|
+
const eager = options.eager ?? false
|
|
301
|
+
const thin = Math.max(0.05, options.thin ?? 1)
|
|
302
|
+
const tolerance = options.tolerance ?? 0
|
|
303
|
+
const seed = options.seed ?? today()
|
|
304
|
+
const geometry = options.into ?? new Float32Array(Math.max(1 << 14, options.room ?? ROOM))
|
|
305
|
+
|
|
306
|
+
let box = { height: options.height, width: options.width }
|
|
307
|
+
let at = 0
|
|
308
|
+
let held = 0
|
|
309
|
+
let turn = 0
|
|
310
|
+
let daylight = 1
|
|
311
|
+
let aimed = -1
|
|
312
|
+
|
|
313
|
+
// Everything that might put something on the sand this frame, gathered into
|
|
314
|
+
// one array that is emptied and filled rather than made again: this is the
|
|
315
|
+
// one list in the scene that is as long as the shoal, and a fresh one of those
|
|
316
|
+
// every frame is a new allocation the size of the whole water sixty times a
|
|
317
|
+
// second.
|
|
318
|
+
const shade: Caster[] = []
|
|
319
|
+
|
|
320
|
+
const put = (value: number) => {
|
|
321
|
+
geometry[at++] = value
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const water = createBiome()
|
|
325
|
+
|
|
326
|
+
const seabed = createSeabed({
|
|
327
|
+
cliffs: spread(PER_K.cliffs, MOST.cliffs, box.width, thin),
|
|
328
|
+
height: box.height,
|
|
329
|
+
ranges: RANGES,
|
|
330
|
+
seed,
|
|
331
|
+
stones: spread(PER_K.stones, MOST.stones, box.width, thin),
|
|
332
|
+
width: box.width,
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
const reef = createReef({
|
|
336
|
+
floor: seabed.floorAt,
|
|
337
|
+
heads: Math.max(LEAST, Math.round(REEF_HEADS * thin)),
|
|
338
|
+
height: box.height,
|
|
339
|
+
seed,
|
|
340
|
+
tolerance,
|
|
341
|
+
width: box.width,
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
const day = thriving(seed)
|
|
345
|
+
|
|
346
|
+
const alive = (kinds: readonly Kind[], least: number, most: number): number =>
|
|
347
|
+
Math.max(least, Math.min(most, Math.round(manyIn(kinds, box.width, box.height, seed))))
|
|
348
|
+
|
|
349
|
+
// Dug before the bed is sown, because the bed asks it where not to grow. The
|
|
350
|
+
// colony reads the water and nothing reads the colony: a garden eel is
|
|
351
|
+
// smaller than anything here that would care, and what a bed of them is to
|
|
352
|
+
// the rest of this sea is sand.
|
|
353
|
+
const eels = createEels({
|
|
354
|
+
about: water.about,
|
|
355
|
+
count: alive(["eel"], COLONY.fewest, COLONY.most),
|
|
356
|
+
floor: seabed.floorAt,
|
|
357
|
+
height: box.height,
|
|
358
|
+
seed,
|
|
359
|
+
tolerance,
|
|
360
|
+
width: box.width,
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
const flora = createFlora({
|
|
364
|
+
about: water.about,
|
|
365
|
+
anemones: lush(PER_K.anemones, MOST.anemones, box.width, day, thin),
|
|
366
|
+
bare: eels.bare,
|
|
367
|
+
corals: lush(PER_K.corals, MOST.corals, box.width, day, thin),
|
|
368
|
+
// A renderer bending the bed on the card takes every plant but the crown as
|
|
369
|
+
// a shape at rest, and the crown is the honest exception: it is a heading
|
|
370
|
+
// that keeps turning rather than a shape with a bend put through it.
|
|
371
|
+
cutting: wired ? ["anemone"] : undefined,
|
|
372
|
+
fans: lush(PER_K.fans, MOST.fans, box.width, day, thin),
|
|
373
|
+
floor: seabed.floorAt,
|
|
374
|
+
grasses: lush(PER_K.grasses, MOST.grasses, box.width, day, thin),
|
|
375
|
+
height: box.height,
|
|
376
|
+
kelps: lush(PER_K.kelps, MOST.kelps, box.width, day, thin),
|
|
377
|
+
seed,
|
|
378
|
+
tolerance,
|
|
379
|
+
width: box.width,
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
const fishCount = (): number => {
|
|
383
|
+
const full = alive(FISH_KINDS, FISH.least, FISH.most)
|
|
384
|
+
const hour = FISH.night + (1 - FISH.night) * daylight
|
|
385
|
+
return Math.max(FISH.least, Math.round(full * hour))
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const moteCount = (): number =>
|
|
389
|
+
Math.max(MOTES.least, Math.min(MOTES.most, Math.round((box.width * box.height) / MOTES.per)))
|
|
390
|
+
|
|
391
|
+
const walkers = createWalkers({
|
|
392
|
+
about: water.about,
|
|
393
|
+
crabs: alive(["crab"], LEAST, CRAWLERS.mostCrabs),
|
|
394
|
+
floor: seabed.floorAt,
|
|
395
|
+
seed,
|
|
396
|
+
starfish: alive(["starfish"], LEAST, CRAWLERS.mostStarfish),
|
|
397
|
+
tolerance,
|
|
398
|
+
width: box.width,
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
// It reads the register and never enters it. Something the size of a fist
|
|
402
|
+
// that has stopped running is not a thing anything else in this water has to
|
|
403
|
+
// make a decision about, which is the whole of what being a pufferfish buys.
|
|
404
|
+
const puffers = createPuffers({
|
|
405
|
+
about: water.about,
|
|
406
|
+
count: alive(["puffer"], 0, PUFFERS.most),
|
|
407
|
+
floor: seabed.floorAt,
|
|
408
|
+
height: box.height,
|
|
409
|
+
seed,
|
|
410
|
+
tolerance,
|
|
411
|
+
width: box.width,
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
const shoal = createShoal({
|
|
415
|
+
count: fishCount(),
|
|
416
|
+
cruise: CRUISE,
|
|
417
|
+
height: box.height,
|
|
418
|
+
seed,
|
|
419
|
+
species: wild(seed),
|
|
420
|
+
width: box.width,
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
const inklings = createCephalopods({
|
|
424
|
+
floor: seabed.floorAt,
|
|
425
|
+
height: box.height,
|
|
426
|
+
octopuses: alive(["octopus"], 1, MOST_INKLINGS),
|
|
427
|
+
seed,
|
|
428
|
+
squids: alive(["squid"], 1, MOST_INKLINGS),
|
|
429
|
+
width: box.width,
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
// The cephalopods, entered from out here rather than from inside their own
|
|
433
|
+
// file. An octopus is measured across its arms rather than its head, which is
|
|
434
|
+
// twice the size it is drawn at and is the honest number: what a crab sees
|
|
435
|
+
// coming over a stone is the reach.
|
|
436
|
+
water.enter(() => {
|
|
437
|
+
const about = []
|
|
438
|
+
for (const pus of inklings.octopuses) {
|
|
439
|
+
if (pus.lift > 0) {
|
|
440
|
+
about.push({ depth: pus.depth, menace: 0.55, size: pus.size * 2, x: pus.x, y: pus.y })
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
for (const squid of inklings.squids) {
|
|
444
|
+
about.push({ depth: squid.depth, menace: 0.3, size: squid.size, x: squid.x, y: squid.y })
|
|
445
|
+
}
|
|
446
|
+
return about
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
const jellies = createJellies({
|
|
450
|
+
count: alive(JELLY_KINDS, 1, JELLIES.most),
|
|
451
|
+
floor: seabed.floorAt,
|
|
452
|
+
height: box.height,
|
|
453
|
+
seed,
|
|
454
|
+
water,
|
|
455
|
+
width: box.width,
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
const wreckage = createRelics({
|
|
459
|
+
floor: seabed.floorAt,
|
|
460
|
+
height: box.height,
|
|
461
|
+
seed,
|
|
462
|
+
width: box.width,
|
|
463
|
+
})
|
|
464
|
+
const passers = createPassers({ eager, height: box.height, seed, width: box.width })
|
|
465
|
+
const visitors = createVisitors({ eager, height: box.height, seed, water, width: box.width })
|
|
466
|
+
const flock = createSwarm({
|
|
467
|
+
count: Math.round(SPECKS * thin),
|
|
468
|
+
eager,
|
|
469
|
+
height: box.height,
|
|
470
|
+
seed,
|
|
471
|
+
water,
|
|
472
|
+
width: box.width,
|
|
473
|
+
})
|
|
474
|
+
const nemos = createNemos({ about: water.about, reef, seed })
|
|
475
|
+
const caustics = createCaustics({ seed })
|
|
476
|
+
const crags = createCrags({ floor: seabed.floorAt, height: box.height, seed, width: box.width })
|
|
477
|
+
const drift = createDrift({
|
|
478
|
+
floor: seabed.floorAt,
|
|
479
|
+
height: box.height,
|
|
480
|
+
motes: Math.round(moteCount() * thin),
|
|
481
|
+
seed,
|
|
482
|
+
tolerance,
|
|
483
|
+
vents: VENTS,
|
|
484
|
+
width: box.width,
|
|
485
|
+
})
|
|
486
|
+
const light = createRays({ count: SHAFTS, height: box.height, seed, width: box.width })
|
|
487
|
+
const clouds = createClouds({ count: CLOUDS, height: box.height, seed, width: box.width })
|
|
488
|
+
|
|
489
|
+
/** Whatever the water is most frightened of this frame. */
|
|
490
|
+
const felt = () => {
|
|
491
|
+
const above = passers.startle
|
|
492
|
+
const among = visitors.startle
|
|
493
|
+
if (!above) return among
|
|
494
|
+
if (!among) return above
|
|
495
|
+
return among.force > above.force ? among : above
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const putPoints = (points: readonly { x: number; y: number }[]) => {
|
|
499
|
+
put(points.length)
|
|
500
|
+
for (const point of points) {
|
|
501
|
+
put(point.x)
|
|
502
|
+
put(point.y)
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const putTwigs = (plant: Plant) => {
|
|
507
|
+
put(plant.twigs.length)
|
|
508
|
+
for (const twig of plant.twigs) {
|
|
509
|
+
const cut = twigOf(twig.d)
|
|
510
|
+
put(twig.width)
|
|
511
|
+
put(cut.length)
|
|
512
|
+
for (const n of cut) put(n)
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** What each anemone was last handed over at, so a still one is not sent
|
|
517
|
+
* again. A plant says when it has recut itself, and nothing else does. */
|
|
518
|
+
const handed: number[] = []
|
|
519
|
+
|
|
520
|
+
return {
|
|
521
|
+
get box() {
|
|
522
|
+
return box
|
|
523
|
+
},
|
|
524
|
+
|
|
525
|
+
geometry,
|
|
526
|
+
|
|
527
|
+
layout(): number {
|
|
528
|
+
at = 0
|
|
529
|
+
put(3)
|
|
530
|
+
put(box.width)
|
|
531
|
+
put(box.height)
|
|
532
|
+
put(1 + seabed.ranges.length + 1 + seabed.cliffs.length)
|
|
533
|
+
put(flora.plants.length)
|
|
534
|
+
|
|
535
|
+
put(GROUND.sand)
|
|
536
|
+
put(1)
|
|
537
|
+
putPoints(seabed.ridge)
|
|
538
|
+
|
|
539
|
+
for (const band of seabed.ranges) {
|
|
540
|
+
put(GROUND.hill)
|
|
541
|
+
put(band.depth)
|
|
542
|
+
putPoints(band.ridge)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
put(GROUND.mound)
|
|
546
|
+
put(reef.depth)
|
|
547
|
+
putPoints(reef.crest)
|
|
548
|
+
|
|
549
|
+
for (const cliff of seabed.cliffs) {
|
|
550
|
+
put(GROUND.cliff)
|
|
551
|
+
put(cliff.depth)
|
|
552
|
+
putPoints(cliff.ridge)
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
for (let p = 0; p < flora.plants.length; p++) {
|
|
556
|
+
const plant = flora.plants[p]
|
|
557
|
+
if (!plant) continue
|
|
558
|
+
put(WIRE_KINDS[plant.kind] ?? 0)
|
|
559
|
+
put(plant.depth)
|
|
560
|
+
put(plant.girth)
|
|
561
|
+
put(plant.scale)
|
|
562
|
+
put(plant.x)
|
|
563
|
+
put(plant.y)
|
|
564
|
+
|
|
565
|
+
if (plant.kind === "coral") {
|
|
566
|
+
putTwigs(plant)
|
|
567
|
+
continue
|
|
568
|
+
}
|
|
569
|
+
if (plant.kind === "anemone") continue
|
|
570
|
+
|
|
571
|
+
const frame = flora.madeOf(p)
|
|
572
|
+
if (!frame) {
|
|
573
|
+
put(0)
|
|
574
|
+
put(0)
|
|
575
|
+
continue
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
put(frame.limbs.length)
|
|
579
|
+
for (const limb of frame.limbs) {
|
|
580
|
+
put(limb.beat)
|
|
581
|
+
put(limb.give)
|
|
582
|
+
put(limb.own)
|
|
583
|
+
put(limb.seat)
|
|
584
|
+
put(limb.shift)
|
|
585
|
+
put(limb.slant)
|
|
586
|
+
put(limb.span)
|
|
587
|
+
put(limb.steps)
|
|
588
|
+
put(limb.stem)
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
put(frame.leaves.length)
|
|
592
|
+
for (const leaf of frame.leaves) {
|
|
593
|
+
put(leaf.limb)
|
|
594
|
+
put(leaf.seat)
|
|
595
|
+
putPoints(leaf.shape)
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return at
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* A frame of the water.
|
|
604
|
+
*
|
|
605
|
+
* ```
|
|
606
|
+
* swayX swayY tilt overscan turn daylight
|
|
607
|
+
* count
|
|
608
|
+
* drawing: form tone weight shade alpha lane width fall fadeTop fadeSpan
|
|
609
|
+
* thin soft n (m (x y)*m)*n
|
|
610
|
+
* ```
|
|
611
|
+
*/
|
|
612
|
+
over(): number {
|
|
613
|
+
at = 0
|
|
614
|
+
const unit = Math.min(box.width, box.height) * SWAY_REACH
|
|
615
|
+
|
|
616
|
+
// Two sines an octave and a bit apart on each axis, which is the cheapest
|
|
617
|
+
// thing that does not read as a pendulum: one alone is a frame swinging,
|
|
618
|
+
// and two whose periods do not divide each other never come back to the
|
|
619
|
+
// same place twice in anything like a minute.
|
|
620
|
+
put(unit * (0.62 * Math.sin(held * 0.11) + 0.38 * Math.sin(held * 0.29 + 1.7)))
|
|
621
|
+
put(unit * (0.62 * Math.sin(held * 0.13 + 2.4) + 0.38 * Math.sin(held * 0.23 + 0.6)))
|
|
622
|
+
put(SWAY_ROLL * Math.sin(held * 0.09 + 1.1))
|
|
623
|
+
|
|
624
|
+
// How much bigger than its box the scene is drawn, so that a frame which
|
|
625
|
+
// has wandered is never a frame with the page showing along one edge. The
|
|
626
|
+
// margin covers the roll as well as the wander: a corner is swung by the
|
|
627
|
+
// angle times its distance from the middle, which is further than the
|
|
628
|
+
// middle goes.
|
|
629
|
+
const reach = unit + (Math.abs(SWAY_ROLL) * Math.hypot(box.width, box.height)) / 2
|
|
630
|
+
put(1 + (2 * reach) / Math.max(1, Math.min(box.width, box.height)))
|
|
631
|
+
put(turn)
|
|
632
|
+
|
|
633
|
+
const lit = at
|
|
634
|
+
put(daylight)
|
|
635
|
+
|
|
636
|
+
const pen = new Pen(geometry, at)
|
|
637
|
+
if (!wired) paintWater(pen, box)
|
|
638
|
+
|
|
639
|
+
const sky = paintSky(pen, box, clouds, options.hour ?? null)
|
|
640
|
+
daylight = sky.daylight
|
|
641
|
+
geometry[lit] = daylight
|
|
642
|
+
|
|
643
|
+
// The shoal follows the light. Reef fish shelter after dark and the water
|
|
644
|
+
// over the sand thins out, so the count is a share of the day's, and the
|
|
645
|
+
// shoal is told the number rather than made to be it: it works towards it
|
|
646
|
+
// from the edges over the minutes dawn takes anyway, because a fish
|
|
647
|
+
// appearing in the middle of the picture is the one thing the water
|
|
648
|
+
// promises never to do.
|
|
649
|
+
if (Math.abs(daylight - aimed) > DAWN_STEP) {
|
|
650
|
+
aimed = daylight
|
|
651
|
+
shoal.hold(fishCount())
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (!wired) {
|
|
655
|
+
paintGround(pen, seabed, reef, box)
|
|
656
|
+
paintFlora(pen, flora)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
paintCrags(pen, crags, box)
|
|
660
|
+
paintStones(pen, seabed)
|
|
661
|
+
paintHeads(pen, reef)
|
|
662
|
+
paintRelics(pen, wreckage)
|
|
663
|
+
// Over the ground and under everything standing on it, which is where
|
|
664
|
+
// light that came through the surface has got to.
|
|
665
|
+
paintCaustics(pen, seabed, caustics, box, daylight)
|
|
666
|
+
|
|
667
|
+
// Under everything that swims and over everything that does not, which is
|
|
668
|
+
// the one place in the order where a shadow on the ground is true. Built
|
|
669
|
+
// here rather than asked of each painter, because what a thing puts on the
|
|
670
|
+
// sand is the same question whatever the thing is.
|
|
671
|
+
shade.length = 0
|
|
672
|
+
for (const one of shoal.fish) shade.push(one)
|
|
673
|
+
for (const one of jellies.jellies) shade.push(one)
|
|
674
|
+
for (const one of puffers.puffers) shade.push(one)
|
|
675
|
+
for (const one of walkers.walkers) shade.push(one)
|
|
676
|
+
for (const one of visitors.crossing) shade.push(one)
|
|
677
|
+
paintShade(pen, shade, seabed.floorAt, box.height)
|
|
678
|
+
|
|
679
|
+
paintRays(pen, light, daylight)
|
|
680
|
+
paintSnow(pen, drift)
|
|
681
|
+
paintShoal(pen, shoal)
|
|
682
|
+
paintNemos(pen, nemos)
|
|
683
|
+
paintSwarm(pen, flock)
|
|
684
|
+
paintInklings(pen, inklings)
|
|
685
|
+
paintJellies(pen, jellies, daylight)
|
|
686
|
+
paintEels(pen, eels)
|
|
687
|
+
paintPuffers(pen, puffers)
|
|
688
|
+
paintWalkers(pen, walkers)
|
|
689
|
+
paintVisitors(pen, visitors)
|
|
690
|
+
paintPassers(pen, passers)
|
|
691
|
+
|
|
692
|
+
at = pen.close()
|
|
693
|
+
return at
|
|
694
|
+
},
|
|
695
|
+
|
|
696
|
+
publish(): number {
|
|
697
|
+
at = 0
|
|
698
|
+
put(flora.plants.length)
|
|
699
|
+
|
|
700
|
+
for (let p = 0; p < flora.plants.length; p++) {
|
|
701
|
+
const plant = flora.plants[p]
|
|
702
|
+
const swing = flora.swinging[p]
|
|
703
|
+
if (!plant) continue
|
|
704
|
+
put(swing ? swing.amp : 0)
|
|
705
|
+
put(swing ? swing.own : 0)
|
|
706
|
+
|
|
707
|
+
if (plant.kind !== "anemone" || handed[p] === plant.cut) {
|
|
708
|
+
put(0)
|
|
709
|
+
continue
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
handed[p] = plant.cut
|
|
713
|
+
put(1)
|
|
714
|
+
putPoints(plant.points)
|
|
715
|
+
put(plant.blades.length)
|
|
716
|
+
for (const blade of plant.blades) putPoints(blade)
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return at
|
|
720
|
+
},
|
|
721
|
+
|
|
722
|
+
resize(width: number, height: number): void {
|
|
723
|
+
box = { height, width }
|
|
724
|
+
seabed.resize(width, height)
|
|
725
|
+
reef.resize(width, height, seabed.floorAt)
|
|
726
|
+
eels.resize(width, height, seabed.floorAt)
|
|
727
|
+
puffers.resize(width, height, seabed.floorAt)
|
|
728
|
+
flora.resize(width, height, seabed.floorAt)
|
|
729
|
+
handed.length = 0
|
|
730
|
+
},
|
|
731
|
+
|
|
732
|
+
seed,
|
|
733
|
+
|
|
734
|
+
step(seconds: number): void {
|
|
735
|
+
// The passers and the visitors go first, so the fish answer the hull and
|
|
736
|
+
// the shark where they are this frame rather than where they were last
|
|
737
|
+
// one. Everything else here is indifferent to the order.
|
|
738
|
+
passers.step(seconds)
|
|
739
|
+
visitors.step(seconds)
|
|
740
|
+
flock.step(seconds, felt())
|
|
741
|
+
shoal.step(seconds, null, felt())
|
|
742
|
+
drift.step(seconds)
|
|
743
|
+
caustics.step(seconds)
|
|
744
|
+
light.step(seconds)
|
|
745
|
+
clouds.step(seconds)
|
|
746
|
+
flora.step(seconds)
|
|
747
|
+
inklings.step(seconds, passers.startle)
|
|
748
|
+
jellies.step(seconds, felt())
|
|
749
|
+
walkers.step(seconds)
|
|
750
|
+
eels.step(seconds)
|
|
751
|
+
puffers.step(seconds)
|
|
752
|
+
reef.step(seconds)
|
|
753
|
+
nemos.step(seconds)
|
|
754
|
+
wreckage.step(seconds)
|
|
755
|
+
|
|
756
|
+
held += seconds
|
|
757
|
+
turn = (turn + 1) % 512
|
|
758
|
+
},
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Carry the whole water forward by a stretch of time, as cheaply as it can
|
|
762
|
+
* be done.
|
|
763
|
+
*
|
|
764
|
+
* Everything that swims has to be swum: where a fish is after a minute is
|
|
765
|
+
* the minute it spent getting there. The bed is the exception, because a
|
|
766
|
+
* plant has no memory. The passers are the other exception, and for the
|
|
767
|
+
* opposite reason: they keep appointments rather than a stopwatch, so
|
|
768
|
+
* winding them here would spend the day's boat on the two minutes of water
|
|
769
|
+
* that exist to be skipped.
|
|
770
|
+
*/
|
|
771
|
+
wind(seconds: number): void {
|
|
772
|
+
const steps = Math.round(Math.max(0, seconds) / WIND_STEP)
|
|
773
|
+
|
|
774
|
+
for (let i = 0; i < steps; i++) {
|
|
775
|
+
if (eager) passers.step(WIND_STEP)
|
|
776
|
+
visitors.step(WIND_STEP)
|
|
777
|
+
flock.step(WIND_STEP, felt())
|
|
778
|
+
shoal.step(WIND_STEP, null, felt())
|
|
779
|
+
drift.step(WIND_STEP)
|
|
780
|
+
light.step(WIND_STEP)
|
|
781
|
+
clouds.step(WIND_STEP)
|
|
782
|
+
inklings.step(WIND_STEP, eager ? passers.startle : null)
|
|
783
|
+
jellies.step(WIND_STEP, felt())
|
|
784
|
+
walkers.step(WIND_STEP)
|
|
785
|
+
eels.step(WIND_STEP)
|
|
786
|
+
puffers.step(WIND_STEP)
|
|
787
|
+
nemos.step(WIND_STEP)
|
|
788
|
+
wreckage.step(WIND_STEP)
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
flora.wind(steps * WIND_STEP)
|
|
792
|
+
reef.wind(steps * WIND_STEP)
|
|
793
|
+
held += steps * WIND_STEP
|
|
794
|
+
},
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* How far to wind a sea that is opening now, in seconds.
|
|
800
|
+
*
|
|
801
|
+
* The wall clock wrapped at `CYCLE`, on top of the settling every scene owes
|
|
802
|
+
* itself. Nothing is written down and nothing is read back: a clock is a clock
|
|
803
|
+
* on every machine, so two boxes opening together open on the same moment of
|
|
804
|
+
* the same water for free, and one opening an hour later does not open on the
|
|
805
|
+
* same picture.
|
|
806
|
+
*/
|
|
807
|
+
export function opening(settle: number): number {
|
|
808
|
+
return settle + (Math.floor(Date.now() / 1000) % CYCLE)
|
|
809
|
+
}
|