@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/README.md CHANGED
@@ -1,3 +1,53 @@
1
- # Temporary Holding Version
1
+ # codin-cod-ornament
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }
@@ -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
+ }