@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/LICENSE +661 -0
- package/README.md +52 -2
- package/package.json +35 -3
- package/src/biome.ts +157 -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/frame.ts +204 -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/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/README.md
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
1
|
-
#
|
|
1
|
+
# codin-cod-ornament
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The sea behind [CodinCod](https://codincod.com): what lives in it, the weather
|
|
4
|
+
over it, and how each thing is drawn. Published as `@codincod/ornament`.
|
|
5
|
+
|
|
6
|
+
Nothing here touches a browser. The modules decide where a fish goes, what the
|
|
7
|
+
sky is doing at this hour, and what each drawing looks like, and hand that to a
|
|
8
|
+
renderer. Two renderers read it:
|
|
9
|
+
|
|
10
|
+
- CodinCod's site, whose `assets/js/ornament/` holds the canvas, SVG and WebGPU
|
|
11
|
+
halves and the LiveView hooks that start them.
|
|
12
|
+
- [seascape](https://github.com/reeveng/seascape), the desktop background, in a
|
|
13
|
+
bare V8 and in Qt's JavaScript engine.
|
|
14
|
+
|
|
15
|
+
`tsconfig.json` leaves the DOM out of `lib`, so `pnpm check` fails on any
|
|
16
|
+
module that reaches for one.
|
|
17
|
+
|
|
18
|
+
## Layout
|
|
19
|
+
|
|
20
|
+
- `src/*.ts`: one simulation per kind of thing in the water or the sky, each
|
|
21
|
+
with its test beside it.
|
|
22
|
+
- `src/paint/`: what one drawing looks like, and `sea.ts`, which builds a whole
|
|
23
|
+
frame out of them.
|
|
24
|
+
- `src/sizes.ts` and `src/census.ts`: how big things are and how many of them
|
|
25
|
+
there are.
|
|
26
|
+
|
|
27
|
+
The package ships its TypeScript source. Import a module by its path:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createShoal } from "@codincod/ornament/shoal.ts"
|
|
31
|
+
import { createSea } from "@codincod/ornament/paint/sea.ts"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Commands
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pnpm install
|
|
38
|
+
pnpm test # node --test over src/**/*.test.ts
|
|
39
|
+
pnpm check # tsc, with no DOM
|
|
40
|
+
pnpm fmt # biome
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Releasing
|
|
44
|
+
|
|
45
|
+
1. Bump `version`, then `pnpm publish`.
|
|
46
|
+
2. In CodinCod, raise the pin in `assets/package.json`. A caret range on a
|
|
47
|
+
`0.x` version stops at the next minor, so `^0.1.0` never picks up 0.2.0.
|
|
48
|
+
3. Run `pnpm install` from CodinCod's root, then `mix assets.build`.
|
|
49
|
+
4. In seascape, rebuild the bundle (`js/build.sh`, then `cargo build --release`).
|
|
50
|
+
|
|
51
|
+
## Licence
|
|
52
|
+
|
|
53
|
+
[AGPL-3.0](LICENSE), the same as CodinCod.
|
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codincod/ornament",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The sea behind CodinCod: what lives in it, the weather over it, and how each thing is drawn, with no DOM",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"codincod",
|
|
7
|
+
"generative",
|
|
8
|
+
"ornament",
|
|
9
|
+
"simulation"
|
|
10
|
+
],
|
|
11
|
+
"author": "CodinCod <hello@codincod.com>",
|
|
12
|
+
"license": "AGPL-3.0-only",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"exports": {
|
|
15
|
+
"./*": "./src/*"
|
|
16
|
+
},
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"files": [
|
|
19
|
+
"src",
|
|
20
|
+
"!src/**/*.test.ts"
|
|
21
|
+
],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@biomejs/biome": "^2.5.15",
|
|
24
|
+
"@types/node": "^26.6.4",
|
|
25
|
+
"typescript": "^7.0.2"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://codincod.com/",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://codeberg.org/reeven/codin-cod-ornament"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"check": "tsc --noEmit -p tsconfig.json",
|
|
34
|
+
"fmt": "biome check --write .",
|
|
35
|
+
"fmt:check": "biome check .",
|
|
36
|
+
"test": "node --test \"src/**/*.test.ts\""
|
|
37
|
+
}
|
|
6
38
|
}
|
package/src/biome.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The water everything is in, and the register of what is in it.
|
|
3
|
+
*
|
|
4
|
+
* Not a conductor. There is no score, nothing here decides what any animal
|
|
5
|
+
* does, and no call in this file makes anything happen. What it holds is a fact
|
|
6
|
+
* every animal in a sea has access to and none of them is told: what else is
|
|
7
|
+
* about, where, and how alarming it looks from a distance.
|
|
8
|
+
*
|
|
9
|
+
* ## Why the water needs a register at all
|
|
10
|
+
*
|
|
11
|
+
* Because the alternative is a director, and a director is what a sea does not
|
|
12
|
+
* have. A shark crossing a reef does not notify the clownfish. It swims, and
|
|
13
|
+
* the reef empties because every fish on it was watching the water and drew its
|
|
14
|
+
* own conclusion, at its own moment, at its own distance. That is why nothing
|
|
15
|
+
* in this ornament exposes a call anybody could fire at it. An animal is handed
|
|
16
|
+
* `about`, looks, and decides.
|
|
17
|
+
*
|
|
18
|
+
* The register is what makes that possible without every animal knowing about
|
|
19
|
+
* every other one. `nemos.ts`, `flora.ts` and `walkers.ts` each grew their own
|
|
20
|
+
* copy of one small shape while waiting for this, and all three said the same
|
|
21
|
+
* thing in their comments: a crab should not be importing a type from a
|
|
22
|
+
* clownfish. `Presence` is that shape, declared once, and a source of them is
|
|
23
|
+
* anything at all.
|
|
24
|
+
*
|
|
25
|
+
* ## A shark at the glass is not near a fish in the murk
|
|
26
|
+
*
|
|
27
|
+
* This water has depth and it is the dimension that gets forgotten,
|
|
28
|
+
* because a screen has only two. Everything in this ornament carries a `depth`:
|
|
29
|
+
* 0 is as far off as the water goes and 1 is up against the glass, and that
|
|
30
|
+
* range is most of the room in the scene. Two things can share a point on the
|
|
31
|
+
* screen and be a long way from each other.
|
|
32
|
+
*
|
|
33
|
+
* So a shark cruising past the camera has no business emptying a reef sitting
|
|
34
|
+
* back in the murk, however neatly the two line up. `abreast` is the share of
|
|
35
|
+
* that, and every reading in this ornament is weighed by it.
|
|
36
|
+
*
|
|
37
|
+
* ## Size and menace are different questions
|
|
38
|
+
*
|
|
39
|
+
* `size` decides how far off something is noticed, because that is what being
|
|
40
|
+
* seen underwater actually depends on. `menace` decides how much it matters
|
|
41
|
+
* once it has been. A turtle and a shark of the same length are the same
|
|
42
|
+
* silhouette at the same distance and they are not the same event, and a sea
|
|
43
|
+
* where they were is a sea where nothing that happens in it means anything.
|
|
44
|
+
*
|
|
45
|
+
* What either number turns into is not settled here. A clownfish quickens at
|
|
46
|
+
* something big and goes home for something hunting; a crab freezes at both; a
|
|
47
|
+
* crown of tentacles minds only what is nearly on top of it; a starfish never
|
|
48
|
+
* reacts to anything, because its answer takes hours and nobody is watching
|
|
49
|
+
* that long. One reading, many answers, and every one of them the animal's own.
|
|
50
|
+
*
|
|
51
|
+
* ## Nothing has to enter
|
|
52
|
+
*
|
|
53
|
+
* A water nobody entered is open water, and everything reading it goes about
|
|
54
|
+
* its day. That is not a degenerate case: it is what most of these scenes are,
|
|
55
|
+
* and it is why every `about` in this ornament is optional.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Something in the water, as much of it as another animal can make out.
|
|
60
|
+
*
|
|
61
|
+
* Where it is, how big it looks, and whether it looks like it hunts. Not what
|
|
62
|
+
* it is. Nothing reading this register can tell a shark from a turtle except by
|
|
63
|
+
* these numbers, which is the right amount for an animal to know: a small fish
|
|
64
|
+
* on a reef has never been told what species anything is either.
|
|
65
|
+
*/
|
|
66
|
+
export interface Presence {
|
|
67
|
+
/** 0 is as far off as this water goes, 1 is up against the glass. */
|
|
68
|
+
readonly depth: number
|
|
69
|
+
/**
|
|
70
|
+
* How much this looks like something that hunts, 0 through 1.
|
|
71
|
+
*
|
|
72
|
+
* 0 is a shape that has never worried anybody, which is most of what crosses
|
|
73
|
+
* a sea. 1 empties a reef. Left out it is 0, so anything entering the water
|
|
74
|
+
* without an opinion about itself is merely large.
|
|
75
|
+
*/
|
|
76
|
+
readonly menace?: number
|
|
77
|
+
/** How big it is across, in px, which is how far off it is noticed from. */
|
|
78
|
+
readonly size: number
|
|
79
|
+
readonly x: number
|
|
80
|
+
readonly y: number
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Something that says where it is when asked.
|
|
85
|
+
*
|
|
86
|
+
* A function rather than a list, because everything in a sea is moving and a
|
|
87
|
+
* list handed over once is a list of where things were. It returns however many
|
|
88
|
+
* it speaks for: one animal, a pod of them, or none at all this moment.
|
|
89
|
+
*/
|
|
90
|
+
export type Source = () => readonly Presence[]
|
|
91
|
+
|
|
92
|
+
export interface Biome {
|
|
93
|
+
/**
|
|
94
|
+
* Everything in the water at this moment.
|
|
95
|
+
*
|
|
96
|
+
* Gathered afresh on every ask, since nothing here keeps a clock and the
|
|
97
|
+
* answer is only true for as long as nothing has moved. The list belongs to
|
|
98
|
+
* the water and is refilled on the next ask, so read it and let it go rather
|
|
99
|
+
* than keeping it.
|
|
100
|
+
*/
|
|
101
|
+
about(): readonly Presence[]
|
|
102
|
+
/**
|
|
103
|
+
* Put something in the water. What comes back takes it out again.
|
|
104
|
+
*
|
|
105
|
+
* Whatever built the animal usually does this, and an animal that does it for
|
|
106
|
+
* itself is better still, since what a thing looks like to something small is
|
|
107
|
+
* a fact about that thing. Nothing checks. A sea is not a guest list.
|
|
108
|
+
*/
|
|
109
|
+
enter(source: Source): () => void
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* How far apart in the water two distances are before they stop mattering.
|
|
114
|
+
*
|
|
115
|
+
* A third of the way through, which is generous and meant to be. The depth of
|
|
116
|
+
* one of these scenes is not measured in anything, so this cannot be a real
|
|
117
|
+
* distance; what it has to do is stop the far murk and the near glass being the
|
|
118
|
+
* same place while leaving anything swimming at roughly your own distance
|
|
119
|
+
* plainly there. Wider than this and depth does nothing. Narrower and animals
|
|
120
|
+
* pass through each other unnoticed, which is worse than the fault it fixes.
|
|
121
|
+
*/
|
|
122
|
+
const REACH = 0.36
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* How nearly abreast two things are, 1 side by side and 0 a water apart.
|
|
126
|
+
*
|
|
127
|
+
* The one piece of arithmetic in this file, and it is geometry rather than
|
|
128
|
+
* judgement: how far apart two things are is the water's own business, and what
|
|
129
|
+
* either of them does about it is not. It falls off rather than cutting off,
|
|
130
|
+
* because a threshold makes a shark switch from harmless to terrifying between
|
|
131
|
+
* two frames of swimming gently forward.
|
|
132
|
+
*/
|
|
133
|
+
export function abreast(depth: number, other: number): number {
|
|
134
|
+
return Math.max(0, 1 - Math.abs(depth - other) / REACH)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function createBiome(): Biome {
|
|
138
|
+
const sources = new Set<Source>()
|
|
139
|
+
const gathered: Presence[] = []
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
about() {
|
|
143
|
+
gathered.length = 0
|
|
144
|
+
for (const source of sources) {
|
|
145
|
+
for (const one of source()) gathered.push(one)
|
|
146
|
+
}
|
|
147
|
+
return gathered
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
enter(source) {
|
|
151
|
+
sources.add(source)
|
|
152
|
+
return () => {
|
|
153
|
+
sources.delete(source)
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
}
|
package/src/caustics.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Light off a moving surface, landing on the ground.
|
|
3
|
+
*
|
|
4
|
+
* A wave is a lens. The top of the water is never flat, so the light coming
|
|
5
|
+
* through it is focused into bright lines and starved between them, and what
|
|
6
|
+
* lands on the sand is a net of them sliding about. It is the one thing that
|
|
7
|
+
* says a sea is lit from a surface rather than merely lighter towards the top.
|
|
8
|
+
*
|
|
9
|
+
* ## Why this is a field and not a net
|
|
10
|
+
*
|
|
11
|
+
* A caustic drawn from above is the net everybody has seen. This water is seen
|
|
12
|
+
* from the side, and a floor seen edge on has no room for a net on it: what
|
|
13
|
+
* reaches the eye is the light on whatever faces upwards, which here is the
|
|
14
|
+
* ridges. So this is asked a place and answers how much light is on it, and the
|
|
15
|
+
* painter walks the ridges it has rather than laying a pattern over the box.
|
|
16
|
+
*
|
|
17
|
+
* ## Why the noise is ridged
|
|
18
|
+
*
|
|
19
|
+
* Smooth noise is cloud. What separates a caustic from a cloud is that it has
|
|
20
|
+
* lines in it: the focusing is sharp and the dark between two bright lines is
|
|
21
|
+
* darker than the average, so the thing to fold is the middle of the range up
|
|
22
|
+
* into a crease and then steepen it. Plain noise put through a curve stays a
|
|
23
|
+
* cloud however hard it is squeezed.
|
|
24
|
+
*/
|
|
25
|
+
import { makeNoise2 } from "./perlin.ts"
|
|
26
|
+
|
|
27
|
+
export interface Caustics {
|
|
28
|
+
/**
|
|
29
|
+
* How much light is on the ground at a place, 0 to 1.
|
|
30
|
+
*
|
|
31
|
+
* `across` is a share of the box and `depth` is how far forward the ground
|
|
32
|
+
* there is, so two ridges at the same x and different distances are lit
|
|
33
|
+
* differently. They have to be: the same pattern on every ridge would flatten
|
|
34
|
+
* the whole bed into one surface, which is the opposite of the errand.
|
|
35
|
+
*/
|
|
36
|
+
at(across: number, depth: number): number
|
|
37
|
+
step(seconds: number): void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CausticsOptions {
|
|
41
|
+
seed: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** How many bright lines fit across the box, and across the water's distance. */
|
|
45
|
+
const CELLS = 7.5
|
|
46
|
+
|
|
47
|
+
const DEEP_CELLS = 2.4
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* How fast the whole pattern slides, in widths a second.
|
|
51
|
+
*
|
|
52
|
+
* Slow. The surface it comes off is moving quickly, but what a caustic does on
|
|
53
|
+
* the ground is mostly breathe: the lines wander and swell where they are
|
|
54
|
+
* rather than travelling, and a pattern crossing the picture at a rate reads as
|
|
55
|
+
* something being projected onto the sand.
|
|
56
|
+
*/
|
|
57
|
+
const DRIFT = 0.045
|
|
58
|
+
|
|
59
|
+
/** How narrow the bright lines are, and how sharply they give out. */
|
|
60
|
+
const CREASE = 2.1
|
|
61
|
+
|
|
62
|
+
const SHARP = 3.2
|
|
63
|
+
|
|
64
|
+
export function createCaustics(options: CausticsOptions): Caustics {
|
|
65
|
+
const noise = makeNoise2(options.seed ^ 0x6c1f)
|
|
66
|
+
let slid = 0
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
at(across: number, depth: number): number {
|
|
70
|
+
const held = noise(across * CELLS + slid, depth * DEEP_CELLS)
|
|
71
|
+
const crease = 1 - Math.abs(held) * CREASE
|
|
72
|
+
if (crease <= 0) return 0
|
|
73
|
+
|
|
74
|
+
return Math.min(1, crease) ** SHARP
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
step(seconds: number): void {
|
|
78
|
+
slid += seconds * DRIFT
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
}
|
package/src/census.ts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much of each animal a sea holds.
|
|
3
|
+
*
|
|
4
|
+
* The other half of `sizes.ts`, and it is here for the same fault. That file
|
|
5
|
+
* was written because every drawing had picked a size that looked right on its
|
|
6
|
+
* own and a boat had come out the size of a turtle. This one is the same story
|
|
7
|
+
* told about how many: every renderer picked a density that read well where it
|
|
8
|
+
* was written, and the water ended up with more crabs on the sand than fish in
|
|
9
|
+
* the column. A crab at eleven per thousand pixels of bed is defensible where
|
|
10
|
+
* it stands. A fish at one per sixty thousand square pixels is defensible where
|
|
11
|
+
* it stands. What lies between them is a sea nobody has ever swum in, and
|
|
12
|
+
* nothing catches it, because the two numbers are never read side by side.
|
|
13
|
+
*
|
|
14
|
+
* So every animal says how many of it a stretch of reef holds, in the unit a
|
|
15
|
+
* survey would report it in, and this turns that into how many to draw.
|
|
16
|
+
*
|
|
17
|
+
* ## Why the numbers are not used as they stand
|
|
18
|
+
*
|
|
19
|
+
* A reef carries upwards of a hundred fish over every hundred square metres of
|
|
20
|
+
* it, and most of them are small ones hanging above the coral. Drawn as
|
|
21
|
+
* counted, that is a screen of fish with a sea somewhere behind them, and
|
|
22
|
+
* everything else in the water is lost among them. Thin the whole table until
|
|
23
|
+
* the fish are countable and the crab, the octopus and the starfish all round
|
|
24
|
+
* to none, which leaves a bare floor under a busy column.
|
|
25
|
+
*
|
|
26
|
+
* The root is the compression that settles it, and it is the same bargain
|
|
27
|
+
* `sizes.ts` strikes over length. Something commoner is still drawn commoner,
|
|
28
|
+
* so the order is never wrong; the ends are pulled in far enough that the rare
|
|
29
|
+
* animal is still in the water somewhere. What is kept is which is commoner,
|
|
30
|
+
* rather than by how much.
|
|
31
|
+
*
|
|
32
|
+
* ## The mix comes out of the same table
|
|
33
|
+
*
|
|
34
|
+
* How common a moon jelly is against a comb jelly is the question of how common
|
|
35
|
+
* a jellyfish is against a fish, asked inside one family. A file that answered
|
|
36
|
+
* it in its own column would answer it a second way by the end of the year, so
|
|
37
|
+
* the shares a family draws its next animal from are these numbers normalised.
|
|
38
|
+
* A family after a count and a family after a mix read one statement.
|
|
39
|
+
*
|
|
40
|
+
* ## A day of its own
|
|
41
|
+
*
|
|
42
|
+
* `plenty.ts` decides how good a year the whole sea is having. This decides
|
|
43
|
+
* which animal is having it. Every kind swings from one day to the next on its
|
|
44
|
+
* own draw, and how far it swings is its own business: blooming is what a
|
|
45
|
+
* jellyfish does and a grouper does not. So `swings` is a column, and a week of
|
|
46
|
+
* water is a comb jelly week and then a moon jelly one.
|
|
47
|
+
*
|
|
48
|
+
* ## What it does not decide
|
|
49
|
+
*
|
|
50
|
+
* Where anything goes, when it turns up, or whether a renderer can afford it.
|
|
51
|
+
* A shark and a manta are in here for their share against each other and for
|
|
52
|
+
* nothing else, because what actually puts one in the water is a rest between
|
|
53
|
+
* crossings (`visitors.ts`); a bed with a thousand plants on it is capped by
|
|
54
|
+
* whoever has to draw them. This is what the sea holds, and the appetite is
|
|
55
|
+
* still `plenty.ts`.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
import { stir } from "./perlin.ts"
|
|
59
|
+
import { thriving } from "./plenty.ts"
|
|
60
|
+
|
|
61
|
+
export type Kind =
|
|
62
|
+
| "barrel"
|
|
63
|
+
| "box"
|
|
64
|
+
| "comb"
|
|
65
|
+
| "crab"
|
|
66
|
+
| "crown"
|
|
67
|
+
| "cruiser"
|
|
68
|
+
| "darter"
|
|
69
|
+
| "dolphin"
|
|
70
|
+
| "drifter"
|
|
71
|
+
| "eel"
|
|
72
|
+
| "escort"
|
|
73
|
+
| "mane"
|
|
74
|
+
| "manowar"
|
|
75
|
+
| "manta"
|
|
76
|
+
| "mermaid"
|
|
77
|
+
| "moon"
|
|
78
|
+
| "octopus"
|
|
79
|
+
| "puffer"
|
|
80
|
+
| "shark"
|
|
81
|
+
| "squid"
|
|
82
|
+
| "starfish"
|
|
83
|
+
| "stinger"
|
|
84
|
+
| "swordfish"
|
|
85
|
+
| "turtle"
|
|
86
|
+
|
|
87
|
+
interface Held {
|
|
88
|
+
/**
|
|
89
|
+
* How many of it live over a hundred square metres of reef.
|
|
90
|
+
*
|
|
91
|
+
* The unit a transect is counted in, so a number here can be argued with by
|
|
92
|
+
* somebody who has swum one. It is what stands in the water at a given
|
|
93
|
+
* moment rather than what passes through in a season: a shark is scarce here
|
|
94
|
+
* because a shark is scarce at any one moment, and it is scarce in the
|
|
95
|
+
* picture for the same reason.
|
|
96
|
+
*/
|
|
97
|
+
readonly holds: number
|
|
98
|
+
/**
|
|
99
|
+
* How hard a day is allowed to swing it, as a power of the day's own draw.
|
|
100
|
+
*
|
|
101
|
+
* At 0 a kind is as common today as it was yesterday, which is what holding a
|
|
102
|
+
* territory looks like from outside. At 1 it rises and falls with the year
|
|
103
|
+
* `plenty.ts` decided. Past that it blooms: the draw is weighted low, so a
|
|
104
|
+
* high power leaves most weeks ordinary or thin and now and then fills the
|
|
105
|
+
* water with one animal, which is the only shape a bloom comes in.
|
|
106
|
+
*/
|
|
107
|
+
readonly swings: number
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* What a reef holds, animal by animal.
|
|
112
|
+
*
|
|
113
|
+
* Read down the `holds` column rather than across a row: the column is the
|
|
114
|
+
* whole point of the file, and every number in it was chosen against its
|
|
115
|
+
* neighbours rather than against how much of the picture the animal deserves.
|
|
116
|
+
* Where a figure is a range in the wild it is the ordinary end of that range,
|
|
117
|
+
* since `swings` is what carries the rest.
|
|
118
|
+
*/
|
|
119
|
+
const SEA: Readonly<Record<Kind, Held>> = {
|
|
120
|
+
/** A dustbin lid of a jelly, in on a season rather than resident. */
|
|
121
|
+
barrel: { holds: 0.09, swings: 1.8 },
|
|
122
|
+
/** Small, scarce, and inshore for part of the year. */
|
|
123
|
+
box: { holds: 0.025, swings: 1.6 },
|
|
124
|
+
/** Comb jellies outnumber everything else adrift in the water. */
|
|
125
|
+
comb: { holds: 0.66, swings: 2 },
|
|
126
|
+
/** Most crabs on a reef are under it. This is the few that are out. */
|
|
127
|
+
crab: { holds: 3, swings: 0.4 },
|
|
128
|
+
/** Deep water, and up here only where cold water is. */
|
|
129
|
+
crown: { holds: 0.03, swings: 1.4 },
|
|
130
|
+
/** Snapper, surgeon, parrot: the fish a reef reads as at a glance. */
|
|
131
|
+
cruiser: { holds: 24, swings: 0.35 },
|
|
132
|
+
/** Damsels and anthias over the coral, which is most of what a reef holds. */
|
|
133
|
+
darter: { holds: 90, swings: 0.5 },
|
|
134
|
+
/** They pass along a coast rather than live on one stretch of it. */
|
|
135
|
+
dolphin: { holds: 0.04, swings: 0.5 },
|
|
136
|
+
/** A grouper holds a hole and there are only so many holes. */
|
|
137
|
+
drifter: { holds: 1.1, swings: 0.15 },
|
|
138
|
+
/** Where there is a bed of them there are hundreds, and where there is not
|
|
139
|
+
* there are none. The colony's own arithmetic thins it to what fits. */
|
|
140
|
+
eel: { holds: 140, swings: 1.2 },
|
|
141
|
+
/** Butterflyfish, which go about in twos and are never far apart. */
|
|
142
|
+
escort: { holds: 6, swings: 0.35 },
|
|
143
|
+
/** A cold water animal, and in warm water a rarity worth the day. */
|
|
144
|
+
mane: { holds: 0.06, swings: 1.8 },
|
|
145
|
+
/** It goes where the wind puts it, so it arrives in a fleet or not at all. */
|
|
146
|
+
manowar: { holds: 0.015, swings: 2.6 },
|
|
147
|
+
/** Scarcest of the large ones, and tied to the few places they gather. */
|
|
148
|
+
manta: { holds: 0.012, swings: 0.7 },
|
|
149
|
+
/** Not an animal and not on a density; `visitors.ts` keeps her calendar. */
|
|
150
|
+
mermaid: { holds: 0, swings: 0 },
|
|
151
|
+
/** The jelly anybody can name, and it is common enough to earn that. */
|
|
152
|
+
moon: { holds: 0.45, swings: 2 },
|
|
153
|
+
/** One to a den, and a den is a find. */
|
|
154
|
+
octopus: { holds: 0.5, swings: 0.25 },
|
|
155
|
+
/** Solitary, territorial, and one to a stretch of reef this size. */
|
|
156
|
+
puffer: { holds: 0.4, swings: 0.4 },
|
|
157
|
+
/** A reef shark works a stretch of reef that is larger than this one. */
|
|
158
|
+
shark: { holds: 0.09, swings: 0.4 },
|
|
159
|
+
/** Reef squid keep company, so where there is one there are a few. */
|
|
160
|
+
squid: { holds: 0.6, swings: 0.8 },
|
|
161
|
+
/** Slow, obvious, and lying in the open, which is why they read as common. */
|
|
162
|
+
starfish: { holds: 4, swings: 0.5 },
|
|
163
|
+
/** The sea nettle, in on the season the way the barrel is. */
|
|
164
|
+
stinger: { holds: 0.18, swings: 1.8 },
|
|
165
|
+
/** A billfish over a reef is a visitor from the water beyond it. */
|
|
166
|
+
swordfish: { holds: 0.05, swings: 0.6 },
|
|
167
|
+
/** They graze a reef and sleep under its ledges, so one is about. */
|
|
168
|
+
turtle: { holds: 0.12, swings: 0.35 },
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* How hard the range is pulled in.
|
|
173
|
+
*
|
|
174
|
+
* The root `sizes.ts` takes of a length, taken here of a density, and picked
|
|
175
|
+
* the same way: at this it is the small fish that carry the water and the crab
|
|
176
|
+
* and the octopus are still in it. Nearer 1 and the darters bury everything
|
|
177
|
+
* else; nearer 0 and every animal in the sea is about as common as every other,
|
|
178
|
+
* which is the picture this file exists to prevent.
|
|
179
|
+
*/
|
|
180
|
+
const ROOT = 0.55
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* How wide a stretch of sea the box is a window on, in metres.
|
|
184
|
+
*
|
|
185
|
+
* It follows from `sizes.ts` rather than being chosen here. A fish of a third
|
|
186
|
+
* of a metre swimming at eight comes out at a fortieth of the box, and a window
|
|
187
|
+
* that fits forty of those across it is a window on about this much water. It
|
|
188
|
+
* is worth knowing that the answer is a room and not an ocean: everything in
|
|
189
|
+
* this scene is close enough to touch, which is why a shark crossing it is an
|
|
190
|
+
* event.
|
|
191
|
+
*/
|
|
192
|
+
const ACROSS = 13
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* How many the sea is drawn with, against how many it holds.
|
|
196
|
+
*
|
|
197
|
+
* The one number the file is tuned by, and it was fixed by the shoal: the
|
|
198
|
+
* desktop's water held about forty fish before any of this was written down, so
|
|
199
|
+
* it holds about forty now, and every other animal moved to meet them.
|
|
200
|
+
*/
|
|
201
|
+
const SCALE = 2.1
|
|
202
|
+
|
|
203
|
+
/** Square metres of reef in view, which a taller window sees more of. */
|
|
204
|
+
function seaIn(width: number, height: number): number {
|
|
205
|
+
return ACROSS * ACROSS * (height / Math.max(1, width))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* A number of this kind's own, so no two kinds have the same day.
|
|
210
|
+
*
|
|
211
|
+
* Off the name rather than off a position in the table, because a position
|
|
212
|
+
* moves when something is added above it, and a sea that changed everywhere
|
|
213
|
+
* because a jellyfish was added is a sea nobody can reason about.
|
|
214
|
+
*/
|
|
215
|
+
function own(kind: Kind): number {
|
|
216
|
+
let run = 0
|
|
217
|
+
for (let at = 0; at < kind.length; at++) run = stir(run + kind.charCodeAt(at))
|
|
218
|
+
return run & 0xffff
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* How common this kind is drawn today: compressed, and then given its day.
|
|
223
|
+
*
|
|
224
|
+
* That order is the whole of it. Compressed first and swung afterwards, a bloom
|
|
225
|
+
* is a bloom; swung first and compressed afterwards, the root takes most of the
|
|
226
|
+
* day back off it again and a week of water is a week of the same water.
|
|
227
|
+
*/
|
|
228
|
+
function drawnHolds(kind: Kind, seed: number): number {
|
|
229
|
+
const held = SEA[kind]
|
|
230
|
+
if (held.holds <= 0) return 0
|
|
231
|
+
return held.holds ** ROOT * thriving(seed, own(kind)) ** held.swings
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* How many of these kinds to draw in a box this size, on this day.
|
|
236
|
+
*
|
|
237
|
+
* Comes back as it falls out rather than rounded, since a caller with a floor
|
|
238
|
+
* and a ceiling of its own wants the raw appetite to apply them to.
|
|
239
|
+
*/
|
|
240
|
+
export function manyIn(
|
|
241
|
+
kinds: readonly Kind[],
|
|
242
|
+
width: number,
|
|
243
|
+
height: number,
|
|
244
|
+
seed: number,
|
|
245
|
+
): number {
|
|
246
|
+
let sum = 0
|
|
247
|
+
for (const kind of kinds) sum += drawnHolds(kind, seed)
|
|
248
|
+
return (SCALE * sum * seaIn(width, height)) / 100
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The shares to draw one of these kinds from, on this day.
|
|
253
|
+
*
|
|
254
|
+
* Weights against each other rather than chances, which is the shape every
|
|
255
|
+
* family in this ornament already wanted. They are not normalised, because
|
|
256
|
+
* nothing that draws from them needs them to be.
|
|
257
|
+
*/
|
|
258
|
+
export function mixOf<K extends Kind>(kinds: readonly K[], seed: number): Record<K, number> {
|
|
259
|
+
const mix = {} as Record<K, number>
|
|
260
|
+
for (const kind of kinds) mix[kind] = drawnHolds(kind, seed)
|
|
261
|
+
return mix
|
|
262
|
+
}
|